<!DOCTYPE html>
<!-- saved from url=(0022)http://localhost:8080/ -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Rose学习手册</title>

<style type="text/css">
#files #readme {
    background-color: #FFFFFF;
    border: 0 none;
    border-radius: 0 0 0 0;
    padding: 30px;
}

body {
    color: #333333;
    font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif;
}

.bubble {
    background: none repeat scroll 0 0 #EEEEEE;
    border-radius: 3px 3px 3px 3px;
    padding: 3px;
}

.markdown-body code, .markdown-body tt {
    margin: 0 2px;
    padding: 0 5px;
    white-space: nowrap;
    border: 1px solid #eaeaea;
    background-color: #f8f8f8;
    border-radius: 3px;
}

.markdown-body pre > code {
    margin: 0;
    padding: 0;
    white-space: pre;
    border: none;
    background: transparent;
}

.markdown-body .highlight pre, .markdown-body pre {
    background-color: #f8f8f8;
    border: 1px solid #ccc;
    font-size: 13px;
    line-height: 19px;
    overflow: auto;
    padding: 6px 10px;
    border-radius: 3px;
}

.markdown-body pre code, .markdown-body pre tt {
    background-color: transparent;
    border: none;
}

.highlight {
    background: #fff;
}

.highlight .c {
    color: #998;
    font-style: italic;
}

.highlight .err {
    color: #a61717;
    background-color: #e3d2d2;
}

.highlight .k {
    font-weight: bold;
}

.highlight .o {
    font-weight: bold;
}

.highlight .cm {
    color: #998;
    font-style: italic;
}

.highlight .cp {
    color: #999;
    font-weight: bold;
}

.highlight .c1 {
    color: #998;
    font-style: italic;
}

.highlight .cs {
    color: #999;
    font-weight: bold;
    font-style: italic;
}

.highlight .gd {
    color: #000;
    background-color: #fdd;
}

.highlight .gd .x {
    color: #000;
    background-color: #faa;
}

.highlight .ge {
    font-style: italic;
}

.highlight .gr {
    color: #a00;
}

.highlight .gh {
    color: #999;
}

.highlight .gi {
    color: #000;
    background-color: #dfd;
}

.highlight .gi .x {
    color: #000;
    background-color: #afa;
}

.highlight .go {
    color: #888;
}

.highlight .gp {
    color: #555;
}

.highlight .gs {
    font-weight: bold;
}

.highlight .gu {
    color: #800080;
    font-weight: bold;
}

.highlight .gt {
    color: #a00;
}

.highlight .kc {
    font-weight: bold;
}

.highlight .kd {
    font-weight: bold;
}

.highlight .kn {
    font-weight: bold;
}

.highlight .kp {
    font-weight: bold;
}

.highlight .kr {
    font-weight: bold;
}

.highlight .kt {
    color: #458;
    font-weight: bold;
}

.highlight .m {
    color: #099;
}

.highlight .s {
    color: #d14;
}

.highlight .na {
    color: #008080;
}

.highlight .nb {
    color: #0086B3;
}

.highlight .nc {
    color: #458;
    font-weight: bold;
}

.highlight .no {
    color: #008080;
}

.highlight .ni {
    color: #800080;
}

.highlight .ne {
    color: #900;
    font-weight: bold;
}

.highlight .nf {
    color: #900;
    font-weight: bold;
}

.highlight .nn {
    color: #555;
}

.highlight .nt {
    color: #000080;
}

.highlight .nv {
    color: #008080;
}

.highlight .ow {
    font-weight: bold;
}

.highlight .w {
    color: #bbb;
}

.highlight .mf {
    color: #099;
}

.highlight .mh {
    color: #099;
}

.highlight .mi {
    color: #099;
}

.highlight .mo {
    color: #099;
}

.highlight .sb {
    color: #d14;
}

.highlight .sc {
    color: #d14;
}

.highlight .sd {
    color: #d14;
}

.highlight .s2 {
    color: #d14;
}

.highlight .se {
    color: #d14;
}

.highlight .sh {
    color: #d14;
}

.highlight .si {
    color: #d14;
}

.highlight .sx {
    color: #d14;
}

.highlight .sr {
    color: #009926;
}

.highlight .s1 {
    color: #d14;
}

.highlight .ss {
    color: #990073;
}

.highlight .bp {
    color: #999;
}

.highlight .vc {
    color: #008080;
}

.highlight .vg {
    color: #008080;
}

.highlight .vi {
    color: #008080;
}

.highlight .il {
    color: #099;
}

.highlight .gc {
    color: #999;
    background-color: #EAF2F5;
}

.type-csharp .highlight .k {
    color: #00F;
}

.type-csharp .highlight .kt {
    color: #00F;
}

.type-csharp .highlight .nf {
    color: #000;
    font-weight: normal;
}

.type-csharp .highlight .nc {
    color: #2B91AF;
}

.type-csharp .highlight .nn {
    color: #000;
}

.type-csharp .highlight .s {
    color: #A31515;
}

.type-csharp .highlight .sc {
    color: #A31515;
}
</style>

<style>[touch-action="none"] {
    -ms-touch-action: none;
    touch-action: none;
}

[touch-action="pan-x"] {
    -ms-touch-action: pan-x;
    touch-action: pan-x;
}

[touch-action="pan-y"] {
    -ms-touch-action: pan-y;
    touch-action: pan-y;
}

[touch-action="scroll"], [touch-action="pan-x pan-y"], [touch-action="pan-y pan-x"] {
    -ms-touch-action: pan-x pan-y;
    touch-action: pan-x pan-y;
}</style>

<style id="style-1-cropbar-clipper">/* Copyright 2014 Evernote Corporation. All rights reserved. */
.en-markup-crop-options {
    top: 18px !important;
    left: 50% !important;
    margin-left: -100px !important;
    width: 200px !important;
    border: 2px rgba(255,255,255,.38) solid !important;
    border-radius: 4px !important;
}

.en-markup-crop-options div div:first-of-type {
    margin-left: 0px !important;
}
</style></head>
<body>
<div id="wrapper">


<div itemtype="http://schema.org/WebPage" itemscope="" class="site hfeed">
<div class="container hentry">
<!-- /.repohead -->

<div data-pjax-container="" id="js-repo-pjax-container">


<!-- block_view_fragment_key: views10/v8/blob:v21:a9af47bc6fba1c67ef92785815f7e5bc -->
<div id="slider">


<div class="frames">
<div data-type="blob" data-title="rose/ebook/rose-handbook.md at d1c0d4398cd976845b33de6fd87d23facf5a7938 · XiaoMi/rose · GitHub" data-permalink-url="/XiaoMi/rose/blob/d1c0d4398cd976845b33de6fd87d23facf5a7938/ebook/rose-handbook.md" data-path="ebook/rose-handbook.md/" class="frame frame-center">

<div class="bubble" id="files">
<div class="file">


<div class="blob instapaper_body" id="readme">
<article itemprop="mainContentOfPage" class="markdown-body entry-content">
<h1>Rose概述</h1>

<p>
    Rose是面向使用Java开发的同仁们的。Rose
    提供的各种特性和约束惯例，目的就是为了使您在能够轻松地开发web程序。如果您觉得Grails的想法很好，您不必转向它，Rose可以给您这种感觉，同时基于您对Java的熟悉，您又能更好地控制Rose。
</p>

<p>
    我们希望Rose对各种技术的整合和规范，能使您摆脱犹豫，摆脱选择的困难，规避没有经验带来的开发风险。Rose不仅整合技术，同时还强调最佳实践，甚至包括名称规范。我们不仅仅只是提供技术，我们还会引导您应该如何使用好技术。
</p>

<p>
    Rose规范了对Spring的使用，虽然大部分时间之内，您可能只是使用 @Autowired 即可，大多数时候的确这样也就够了。但 Rose 也允许您放置applicationContext-xxx.xml文件来扩展Rose。
</p>

<p>
    不熟悉Spring的人，不用去重温Spring的知识也能够开始，并书写漂亮的程序！对熟悉Spring的人，则你们可以看到更多。
</p>
<ul>
    <li><strong>本手册综合了以下3个地址的Rose学习内容，可忽略该3个地址，直接参考本手册，即可。</strong></li>
    <li>Rose项目源代码Google地址：<a href="http://code.google.com/p/paoding-rose/" target="_blank">http://code.google.com/p/paoding-rose/</a>
    </li>
    <li>Rose项目源代码GitHub地址：<a href="https://github.com/XiaoMi/rose">https://github.com/XiaoMi/rose</a></li>
    <li>Rose-Example项目源代码GitHub地址：<a href="https://github.com/XiaoMi/rose/tree/master/rose-example">https://github.com/XiaoMi/rose/tree/master/rose-example</a>
    </li>
    <li>目标：光大rose在国内java行业的使用，降低java入门。</li>
    <li>人人网、糯米网释出的、开源的高效Java
        web开发框架。在小米米聊服务端再次被验证和使用。一个从零开始的创业公司，在大家技术背景不一的情况下，rose很简单快速地传达到了大家中间。本手册致力于让php开发人员也能快速使用上java开发高性能服务。
    </li>
</ul>
<h1>章节计划</h1>
<ul>
<li>
    第零章：废话少说，直接上Demo
    <ul>
        <li>
            0.1 目的
        </li>
        <li>
            0.2 业务介绍
            <ul>
                <li>0.2.1 地址规定</li>
                <li>0.2.2 登录规定</li>
                <li>0.2.3 功能</li>
            </ul>
        </li>
        <li>
            0.3 URI设计
        </li>
        <li>
            0.4 Controller设计
            <ul>
                <li>0.4.1 HomeController.java</li>
                <li>0.4.2 book.BookController.java</li>
                <li>0.4.3 book.RemarkController.java</li>
                <li>0.4.4 login.LoginController.java</li>
                <li>0.4.5 user.UserController.java</li>
                <li>0.4.6 logs.LogsController.java</li>
            </ul>
        </li>
        <li>
            0.5 拦截器设计
            <ul>
                <li>0.5.1 AutoLogInterceptor.java</li>
                <li>0.5.2 PassportInterceptor.java</li>
                <li>0.5.3 LoginRequredInterceptor.java</li>
                <li>0.5.4 LoginRequired.java</li>
            </ul>
        </li>
        <li>
            0.6 数据库表设计
        </li>
        <li>
            0.7 DAO设计
            <ul>
                <li>0.7.1 BookDAO.java</li>
                <li>0.7.2 OperLogDAO.java</li>
                <li>0.7.3 RemarkDAO.java</li>
                <li>0.7.4 UserDAO.java</li>
            </ul>
        </li>
        <li>
            0.8 Service设计
            <ul>
                <li>0.8.1 BookServiceImpl.java</li>
                <li>0.8.2 OperLogServiceImpl.java</li>
                <li>0.8.3 RemarkServiceImpl.java</li>
                <li>0.8.4 UserServiceImpl.java</li>
            </ul>
        </li>
        <li>
            0.9 资源配置
            <ul>
                <li>0.9.1 数据源配置applicationContext-datasource.xml</li>
                <li>0.9.2 执行器配置applicationContext-executor.xml</li>
            </ul>
        </li>
        <li>
            0.10 视图设计
        </li>
    </ul>
</li>
<li>
    第一章：入门指引
    <ul>
        <li>
            1.1 Rose简介
            <ul>
                <li>1.1.1 Rose是什么</li>
                <li>1.1.2 Rose能做什么</li>
            </ul>
        </li>
        <li>
            1.2 简明教程
            <ul>
                <li>1.2.1 需要些什么</li>
            </ul>
        </li>
    </ul>
</li>

<li>
    第二章：配置与使用
    <ul>
        <li>2.1 点火：基础的pom文件</li>
        <li>2.2 松离合：必不可少的web.xml</li>
        <li>2.3 踩油门：applicationContext.xml</li>
        <li>2.4 hello world</li>
        <li>
            2.5 如何在已有的web应用中使用rose
            <ul>
                <li>2.5.1 Spring集成问题</li>
                <li>2.5.2 RoseFilter问题</li>
            </ul>
        </li>
    </ul>
</li>

<li>
    第三章：框架功能
    <ul>
        <li>
            3.1 controller层：url对照规则与返回结果规则
            <ul>
                <li>3.1.1 url对照规则——最简单的例子</li>
                <li>3.1.2 返回结果规则</li>
                <li>3.1.3 原理</li>
            </ul>
        </li>
        <li>
            3.2 controller层：进阶
            <ul>
                <li>3.2.1 起步</li>
                <li>3.2.2 返回一个velocity页面</li>
                <li>3.2.3 返回一个jsp页面</li>
                <li>3.2.4 在页面渲染业务数据</li>
                <li>3.2.5 重定向(Redirect)</li>
                <li>3.2.6 转发(Forward)</li>
                <li>3.2.7 自定义方法映射</li>
                <li>3.2.8 使用正则表达式自定义方法映射</li>
                <li>3.2.9 获取request请求参数</li>
                <li>3.2.10 数组参数</li>
                <li>3.2.11 Map参数</li>
                <li>3.2.12 表单提交</li>
                <li>3.2.13 返回json</li>
                <li>3.2.14 返回xml</li>
            </ul>
        </li>
        <li>
            3.3 controller层：拦截器支持
            <ul>
                <li>3.3.1 拦截器作用</li>
                <li>3.3.2 拦截器例子</li>
                <li>3.3.3 实际应用场景</li>
            </ul>
        </li>
        <li>
            3.4 controller层：ErrorHandler支持
            <ul>
                <li>3.4.1 ErrorHandler的作用</li>
                <li>3.4.2 ErrorHandler的示例</li>
            </ul>
        </li>
        <li>
            3.5 controller层：自定义http参数支持
            <ul>
                <li>3.5.1 http参数支持的一些前言</li>
                <li>3.5.2 看一个例子</li>
                <li>3.5.3 rose内置的参数支持</li>
            </ul>
        </li>
        <li>
            3.6 controller层：统一的参数验证办法
            <ul>
                <li>3.6.1 用来做什么</li>
                <li>3.6.2 怎么用</li>
                <li>3.6.3 使用时action长什么样</li>
                <li>3.6.4 @IfParamExists的使用</li>
            </ul>
        </li>
        <li>
            3.7 controller层：一闪而过的信息，flash支持
            <ul>
                <li>3.7.1 需求描述</li>
                <li>3.7.2 使用过程</li>
                <li>3.7.3 注意事项</li>
            </ul>
        </li>
        <li>
            3.8 controller层：门户必备portal支持
            <ul>
                <li>3.8.1 什么是portal</li>
                <li>3.8.2 Portal原理</li>
                <li>3.8.3 技术决策</li>
                <li>3.8.4 使用例子</li>
                <li>3.8.5 高级话题</li>
                <li>3.8.6 这样子做的好处</li>
            </ul>
        </li>
        <li>
            3.9 controller层：门户必备pipe支持
            <ul>
                <li>3.9.1 什么是pipe</li>
                <li>3.9.2 与facebook的bigpipe相比rose pipe如何</li>
                <li>3.9.3 看实例</li>
                <li>3.9.4 总结</li>
            </ul>
        </li>
        <li>
            3.10 controller层：上传文件
            <ul>
                <li>3.10.1 其实很简单</li>
                <li>3.10.2 其他</li>
            </ul>
        </li>
        <li>
            3.11 controller层：控制器类详解
            <ul>
                <li>3.11.1 定义</li>
                <li>3.11.2 创建控制器类</li>
                <li>3.11.3 控制器映射规则</li>
                <li>3.11.4 控制器域成员(Field)</li>
                <li>3.11.5 控制器构造器(Constructor)</li>
                <li>3.11.6 控制器方法成员(Method)</li>
                <li>3.11.7 action方法规范</li>
                <li>3.11.8 action方法命名建议(建议而已)</li>
                <li>3.11.9 action方法映射规则</li>
                <li>3.11.10 action方法参数规范</li>
                <li>3.11.11 action方法参数说明</li>
            </ul>
        </li>
        <li>
            3.12 controller层：Invocation详解
            <ul>
                <li>3.12.1 Invocation是什么</li>
                <li>3.12.2 Invocation在哪里使用</li>
                <li>3.12.3 inv.addModel 与 request.setAttribute</li>
            </ul>
        </li>
    </ul>
</li>

<li>
    第四章 DAO层：DAO的基本配置与使用
    <ul>
        <li>
            4.1 jade
            <ul>
                <li>4.1.1 什么是jade</li>
                <li>4.1.2 引入基础配置</li>
            </ul>
        </li>
        <li>
            4.2 jade DAO
            <ul>
                <li>4.2.1 简单的DAO接口</li>
                <li>4.2.2 具有继承关系的DAO接口</li>
                <li>4.2.3 DAO方法</li>
                <li>4.2.4 DAO方法规范</li>
                <li>4.2.5 两种SQL类型</li>
                <li>4.2.6 @SQL的基本用法</li>
                <li>4.2.7 DAO 方法的返回类型</li>
                <li>4.2.8 Bean映射规则(默认)</li>
                <li>4.2.9 自定义 Bean 映射</li>
                <li>4.2.10 方法参数</li>
            </ul>
        </li>
        <li>
            4.3 第一个读取数据库的实例
        </li>
        <li>
            4.4 SQLParam介绍：DAO方法传递参数
        </li>
        <li>
            4.5 表达式的支持
        </li>
        <li>
            4.3 第一个读取数据库的实例
        </li>
    </ul>
</li>

<li>
    第五章 DAO层：分表设置
    <ul>
        <li>
            5.1 散表功能
        </li>
        <li>
            5.2 分表的常规做法
            <ul>
                <li>5.2.1 使用分表第一步：添加新的依赖</li>
                <li>5.2.2 使用分表第二步：设置applicationContext.xml分表规则</li>
                <li>5.2.3 使用分表第三步：bmwutils支持的分表办法</li>
                <li>5.2.4 第一个分表的实例</li>
            </ul>
        </li>
    </ul>
</li>

<li>
    第六章 DAO层：分库设置、读写分离
    <ul>
        <li>
            6.1 数据源配置
        </li>
        <li>
            6.2 DAO接口：数据源使用
        </li>
    </ul>
</li>

<li>
    第七章 安全
</li>

<li>
    第八章 FAQ 常见问题
    <ul>
        <li>
            8.1 如何打一个可被rose识别的jar包
        </li>
        <li>
            8.2 被认成batch执行的sql返回
        </li>
        <li>
            8.3 一个良好的大型WEB项目架构实践
        </li>
        <li>
            8.4 为何DAO不被初始化
        </li>
    </ul>
</li>
</ul>
<h1>第零章：废话少说，直接上Demo</h1>
<h2>0.1 目的</h2>
<p>这个示例内容会比较丰富，通过这个示例的了解，我相信您将能够较为熟练地使用rose开发。</p>
<h2>0.2 业务介绍</h2>
<p>这是一个图书库系统，但仅为演示之用，很显然的，它不是真的是一个符合现实的系统。</p>
<h3>0.2.1 地址规定</h3>
<p>
    访问地址：<a href="http://localhost:8080/lib" target="_blank">http://localhost:8080/lib</a>
</p>
<h3>0.2.2 登录规定</h3>
<p>
    进入图书库系统必须进行登录，但一旦登录之后不同用户之间的权限没有太大区别，但是所有的操作都有记录。这些操作记录是只读记录，且只有一个名为rose的用户才能看到。
</p>
<p>
    用户的帐号由rose用户进行创建和注销。
</p>
<p>
    用户登录到系统，要列出所有的书本清单，每页30本。
</p>
<h3>0.2.3 功能</h3>
<p>
    任何用户可增加书、更改书的信息，但只有rose用户才能删书。
</p>
<p>
    任何用户都可以为书编写备注、备注的个数不限制个数。任何人不能删除备注，包括自己的备注，但rose管理员可以删除。
</p>
<h2>0.3 URI设计</h2>
<pre>    <code>
        | 01 | GET    | <a href="http://localhost:8080/lib/" target="_blank">http://localhost:8080/lib/</a>                                               | 跳转到/lib/book；
        | 02 | GET    | <a href="http://localhost:8080/lib/book" target="_blank">http://localhost:8080/lib/book</a>                                           | 按页浏览书库的书，参数byBookId用于定位与分页；
        | 03 | POST   | <a href="http://localhost:8080/lib/book" target="_blank">http://localhost:8080/lib/book</a>                                           | 增加一本书，参数为name、author、price，都是必填项目；
        | 04 | GET    | <a href="http://localhost:8080/lib/book/add" target="_blank">http://localhost:8080/lib/book/add</a>                                       | 显示增加书的页面；
        | 05 | GET    | <a href="http://localhost:8080/lib/book/%7BbookId%7D" target="_blank">http://localhost:8080/lib/book/{bookId}</a>                                  | 展示该书的详细信息，包括附属的所有评论，最新的备注在前；
        |    |        |                                                                          | 如果带有edit参数，则返回编辑页面，页面不包含评论；
        | 06 | PUT    | <a href="http://localhost:8080/lib/book/%7BbookId%7D?_method=PUT" target="_blank">http://localhost:8080/lib/book/{bookId}?_method=PUT</a>                      | 修改该书的信息；
        | 07 | DELETE | <a href="http://localhost:8080/lib/book/%7BbookId%7D/remark?_method=DELETE" target="_blank">http://localhost:8080/lib/book/{bookId}/remark?_method=DELETE</a>            | 删除该书的所有备注；
        | 08 | DELETE | <a href="http://localhost:8080/lib/book/%7BbookId%7D/remark/%7BremarkId%7D?_method=DELETE" target="_blank">http://localhost:8080/lib/book/{bookId}/remark/{remarkId}?_method=DELETE</a> | 删除某一个备注；
        | 09 | GET    | <a href="http://localhost:8080/lib/login" target="_blank">http://localhost:8080/lib/login</a>                                          | 返回登录页面；
        | 10 | POST   | <a href="http://localhost:8080/lib/login" target="_blank">http://localhost:8080/lib/login</a>                                          | 执行登录判断；
        | 11 | GET    | <a href="http://localhost:8080/lib/user" target="_blank">http://localhost:8080/lib/user</a>                                           | 列出所有用户；
        | 12 | POST   | <a href="http://localhost:8080/lib/user" target="_blank">http://localhost:8080/lib/user</a>                                           | 新增一个注册用户；
        | 13 | GET    | <a href="http://localhost:8080/lib/user/add" target="_blank">http://localhost:8080/lib/user/add</a>                                       | 显示增加用户的页面；
        | 14 | GET    | <a href="http://localhost:8080/lib/user/%7BuserId%7D" target="_blank">http://localhost:8080/lib/user/{userId}</a>                                  | 显示某个用户的详细信息；如果带有edit参数，则返回编辑页面；
        | 15 | DELETE | <a href="http://localhost:8080/lib/user/%7BuserId%7D?_method=DELETE" target="_blank">http://localhost:8080/lib/user/{userId}?_method=DELETE</a>                   | 注销某个用户；
        | 16 | GET    | <a href="http://localhost:8080/lib/logs" target="_blank">http://localhost:8080/lib/logs</a>                                           | 按页列出所有用户操作日志；
    </code>
</pre>
<p><strong>严重说明 :)</strong></p>
<p>上面这个表格中使用了目前浏览器不能支持的PUT、DELETE两个方法，您有两种选择：</p>
<ul>
    <li><strong>选择一：不使用PUT、DELETE：在这种选择下，建议您改为POST，但需要在URI增加一级/update、/delete</strong></li>
    <li><strong>选择二：使用PUT、DELETE：在这种选择下，则请求需要以POST提交，并且在请求URL后增加一个：key为_method，value为PUT或DELETE，如：/update?_method=PUT</strong></li>
</ul>
<p>
    本应用选择了第二种选择(并不是第一种就是不好的，这只是一个权衡，而且无论选择哪一种优缺点都很明显)。
</p>
<h2>0.4 Controller设计</h2>
<h3>0.4.1 HomeController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib;

        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Get;

        @LoginRequired
        @Path("")
        public class HomeController {
            // 1)
            // 这个"1)"表示这个对应于《URI设计》表格中的序号
            @Get
            public String redirect() {
                return "r:/lib/book";
            }
        }
    </code>
</pre>
<h3>0.4.2 book.BookController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib.book;

        import net.paoding.rose.web.annotation.Param;
        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Get;
        import net.paoding.rose.web.annotation.rest.Post;
        import net.paoding.rose.web.annotation.rest.Put;
        import net.paoding.rose.web.var.Model;

        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;

        import com.qd.controllers.lib.LoginRequired;
        import com.qd.lib.model.Book;
        import com.qd.lib.service.BookService;

        @LoginRequired
        @Path("")
        public class BookController {

            @Autowired
            private BookService bookService;

            // 2)
            // @Get 表示对空串的Get请求可有此方法处理
            @Get
            public String list(Model model, @Param("limit") int limit) {
                List&lt;Book&gt; bookList = bookService.find(limit);
                model.add("book-list", bookList.toString());
                return "book-list";
            }

            // 3)
            @Post
            public String add(Book book) {

                long id = bookService.save(book);

                if (id &gt; 0) {
                    return "r:/lib/book";
                } else {
                    return "book-add";
                }
            }

            // 4)
            // @Get("add") 表示对"add"的GET请求，可有此方法处理
            @Get("add")
            public String showAdd() {
                return "book-add";
            }

            // 5)
            // 可以通过大括号表示一个变量，通过冒号说明他的正则规则
            // 正则通常是可以省略的(默认规则是除‘\’的任何的多个字符)，就像后面还有的{remarkId}，虽然remarkId也应该只是数字
            @Get("{bookId:[0-9]+}")
            public String show(Model model, @Param("bookId") long bookId, @Param("edit") boolean isEdit) {
                Book book = bookService.get(bookId);
                model.add("book", book);

                if (isEdit) {
                    return "book-edit";
                }
                    return "book-page";
            }

            // 6)
            // 这里使用目前浏览器无法支持的@Put，你懂的，详看《URI设计》表格中的《严重说明》:)
            @Put("{bookId:[0-9]+}")
            public String update(@Param("bookId") long bookId, Book book) {
                bookService.modify(book);
                return "r:/lib/book/" + bookId;
            }
        }

    </code>
</pre>
<h3>0.4.3 book.RemarkController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib.book;

        import net.paoding.rose.web.annotation.Param;
        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Delete;

        import org.springframework.beans.factory.annotation.Autowired;

        import com.qd.controllers.lib.LoginRequired;
        import com.qd.lib.service.RemarkService;

        @LoginRequired
        @Path("{bookId:[0-9]+}/remark")
        public class RemarkController {

            @Autowired
            private RemarkService remarkService;

            // 7)
            @Delete
            public String clear(@Param("bookId") long bookId) {
                remarkService.removeByBook(bookId);
                return "r:/lib/book/" + bookId;
            }

            // 8)
            @Delete("{remarkId}")
            public String delete(@Param("bookId") long bookId, @Param("remarkId") long remarkId) {
                remarkService.remove(remarkId);
                return "r:/lib/book/" + bookId;
            }
        }
    </code>
</pre>
<h3>0.4.4 login.LoginController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib.login;

        import net.paoding.rose.web.annotation.rest.Get;
        import net.paoding.rose.web.annotation.rest.Post;

        public class LoginController {

            // 9)
            @Get
            public String show() {
                return "@login";
            }

            // 10)
            @Post
            public String doLogin() {
                return "r:/lib/book";
            }
        }
    </code>
</pre>
<h3>0.4.5 user.UserController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib.user;

        import net.paoding.rose.web.annotation.Param;
        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Delete;
        import net.paoding.rose.web.annotation.rest.Get;
        import net.paoding.rose.web.annotation.rest.Post;

        import java.util.Date;
        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;

        import com.qd.controllers.lib.LoginRequired;
        import com.qd.lib.model.User;
        import com.qd.lib.service.UserService;

        @LoginRequired
        @Path("")
        public class UserController {

            @Autowired
            private UserService userService;

            // 11)
            @Get
            public String list() {
                List&lt;User&gt; userList = userService.find();
                return "@user-list:" + userList.toString();
            }

            // 12)
            @Post
            public String add(User user) {

                user.setCreateTime(new Date());

                long id = userService.save(user);
                if (id &gt; 0) {
                    return "r:/lib/user";
                } else {
                    return "@user-add";
                }
            }

            // 13)
            @Get("add")
            public String showAdd() {
                return "@user-add";
            }

            // 14)
            @Get("{userId}")
            public String show(@Param("userId") String userId, @Param("isEdit") boolean isEdit) {
                if (isEdit) {
                    return "@user-edit";
                }
                return "@user-page";
            }

            // 15)
            @Delete("{userId}")
            public String delete(@Param("userId") String userId) {
                return "r:/lib/user";
            }
        }
    </code>
</pre>
<h3>0.4.6 logs.LogsController.java</h3>
<pre>    <code>
        package com.qd.controllers.lib.logs;

        import net.paoding.rose.web.annotation.Param;
        import net.paoding.rose.web.annotation.rest.Get;

        import org.springframework.beans.factory.annotation.Autowired;

        import com.qd.controllers.lib.LoginRequired;
        import com.qd.lib.service.OperLogService;

        @LoginRequired
        public class LogsController {

            @Autowired
            private OperLogService operLogService;

            // 16)
            @Get
            public String list(@Param("offset") int offset) {
                operLogService.find();
                return "@logs-list";
            }
        }

    </code>
</pre>
<h2>0.5 拦截器设计</h2>
<h3>0.5.1 AutoLogInterceptor.java</h3>
<pre>    <code>
        // <strong>拦截器放在controllers包下，称为局部拦截器，局部拦截器只作用于所在目录以及子目录的控制器</strong>
        package com.qd.controllers.lib;

        import net.paoding.rose.web.ControllerInterceptorAdapter;
        import net.paoding.rose.web.Invocation;
        import net.paoding.rose.web.annotation.Interceptor;

        import java.util.concurrent.ExecutorService;

        import org.springframework.beans.factory.annotation.Autowired;

        import com.qd.lib.model.OperLog;
        import com.qd.lib.model.User;
        import com.qd.lib.service.OperLogService;

        @Interceptor(oncePerRequest = true)
        public class AutoLogInterceptor extends ControllerInterceptorAdapter {

            // <strong>这里的executorService，在applicationContext-executor.xml中并没有id="xxx", 但这没有问题，Spring会给该Bean一个默认的id（类全名）</strong>
            @Autowired
            private ExecutorService executorService;

            @Autowired
            private OperLogService operLogService;

            public void afterCompletion(Invocation inv,Throwable ex)throws Exception{

                final OperLog operLog=new OperLog();
                operLog.setResourcePattern(inv.getResourceId());
                operLog.setResourceId(inv.getRequestPath().getUri());
                operLog.setSuccess(ex==null? 1 : 0);
                operLog.setRemarks(ex==null? "" : ex.getMessage());
                operLog.setUserName("TaoMingkai");

                // 封装为一个任务
                Runnable task=new Runnable(){
                    public void run(){
                        operLogService.save(operLog);
                    }
                };

                // 将插入到数据库的操作提交executorService做异步更新
                // <strong>在实际场景中，这种方式要注意webapp shutdown的时候，还未执行的Task的处理问题</strong>
                executorService.submit(task);
            }
        }
    </code>
</pre>
<h3>0.5.2 PassportInterceptor.java</h3>
<pre>    <code>
        package com.qd.controllers.lib;

        import net.paoding.rose.web.ControllerInterceptorAdapter;
        import net.paoding.rose.web.Invocation;
        import net.paoding.rose.web.annotation.Interceptor;

        import com.qd.lib.model.User;

        // <strong>设置oncePerRequest为true，表示如果当前的请求如果是被forward、include转来的，并且之前已经执行了该拦截器，则当前不再过该拦截器，在大部分情况下可以这样理解“一个请求只执行一次”</strong>
        @Interceptor(oncePerRequest = true)
        public class PassportInterceptor extends ControllerInterceptorAdapter {

            public PassportInterceptor() {
                // <strong>设置优先级，优先级越高的拦截器，before方法越先执行</strong>
                setPriority(1000);
            }

            // <strong>before方法在调用控制器方法前执行，相反的after则是控制器执行后才执行</strong>
            @Override
            protected Object before(Invocation inv) throws Exception {
                User loginUser = new User();
                loginUser.setName("TaoMingkai");
                if (loginUser != null) {
                    inv.getRequest().setAttribute("loginUser", loginUser);
                }
                return true;
            }
        }
    </code>
</pre>
<h3>0.5.3 LoginRequiredInterceptor.java</h3>
<pre>    <code>
        package com.qd.controllers.lib;

        import net.paoding.rose.web.ControllerInterceptorAdapter;
        import net.paoding.rose.web.Invocation;
        import net.paoding.rose.web.annotation.Interceptor;

        import java.lang.annotation.Annotation;

        import com.qd.lib.model.User;

        @Interceptor(oncePerRequest = true)
        public class LoginRequiredInterceptor extends ControllerInterceptorAdapter {

            public LoginRequiredInterceptor() {
                setPriority(900);
            }

            // <strong>覆盖这个方法返回一个注解类，使得只有注解了该annotation的方法才会被起作用(注解在控制器类或方法上均有效)</strong>
            // <strong>还有一个相反功能的方法：getDenyAnnotationClass，表示注解了某个Annotation后，拦截器不要拦截他</strong>
            @Override
            protected Class&lt;? extends Annotation&gt; getRequiredAnnotationClass() {
                return LoginRequired.class;
            }

            @Override
            protected Object before(Invocation inv) throws Exception {
                User loginUser = (User) inv.getRequest().getAttribute("loginUser");
                // 如果当前没有登录就返回"r:/lib/login"表示重定向到http://host:port/lib/login页面
                if (loginUser == null) {
                    // <strong>没有返回true或null，表示要中断整个处理流程，即不再继续调用其他拦截器以及最终的控制器</strong>
                    return "r:/lib/login";
                }
                // <strong>返回true或null，表示继续整个流程</strong>
                return true;
            }
        }
    </code>
</pre>
<h3>0.5.4 LoginRequired.java</h3>
<pre>    <code>
        package com.qd.controllers.lib;

        import java.lang.annotation.*;

        // 这是一个annotation，所谓annotation就是一个“标签”，他的职责是“表明”
        // 至于表明之后该怎么样？则由其他代码来处理
        // 对于LoginRequired具体的处理代码是LoginRequiredInterceptor.java
        @Inherited
        @Target( { ElementType.TYPE, ElementType.METHOD })
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface LoginRequired {
        }
    </code>
</pre>
<h2>0.6 数据库表设计</h2>
<pre>    <code>
        user表：
        create table user (
            id bigint not null primary key auto_increment,
            login_name varchar(255) not null,
            password varchar(255) not null,
            name varchar(255) not null,
            create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        book表：
        create table book(
            id bigint not null primary key auto_increment,
            name varchar(255) not null,
            price varchar(255) not null,
            author varchar(255) not null,
            create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        remark表：
        create table remark(
            id bigint not null primary key auto_increment,
            user_name varchar(255) not null,
            book_id  bigint,
            essay varchar(2000) not null,
            create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        oper_log表：
        create table oper_log(
            id bigint not null primary key auto_increment,
            user_name varchar(255) not null,
            resource_pattern varchar(255) not null,
            resource_id varchar(255) not null,
            success tinyint(1)  not null,
            remarks varchar(255) not null,
            create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    </code>
</pre>
<h3></h3>
<h2>0.7 DAO设计</h2>
<h3>0.7.1 BookDAO.java</h3>
<pre>    <code>
        package com.qd.lib.dao;

        import net.paoding.rose.jade.annotation.DAO;
        import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
        import net.paoding.rose.jade.annotation.SQL;

        import java.util.List;

        import com.qd.lib.model.Book;

        // <strong>必须是接口，并且以大写DAO结尾</strong>
        // <strong>必须标注@DAO，DAO中有一个catalog属性，对于大部分人来说，这个都是没用的</strong>
        @DAO
        public interface BookDAO {

            @SQL("select id, name, price, author from book where id = :1")
            public Book get(long bookId);

            @SQL("select id, name, price, author from book order by id desc limit :1")
            public List&lt;Book&gt; query(int limit);

            @SQL("select id, name, price, author from book where id &lt; :1 order by id desc limit :2")
            public List&lt;Book&gt; query(long bookId, int limit);

            @SQL("update book set name=:1.name, price=:1.price, author=:1.author where id=:1.id")
            public void update(Book book);

            @ReturnGeneratedKeys
            @SQL("insert into book (name, price, author) values (:1.name, :1.price, :1.author)")
            public long insert(Book book);
        }
    </code>
</pre>
<h3>0.7.2 OperLogDAO.java</h3>
<pre>    <code>
        package com.qd.lib.dao;

        import net.paoding.rose.jade.annotation.DAO;
        import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
        import net.paoding.rose.jade.annotation.SQL;

        import java.util.List;

        import com.qd.lib.model.OperLog;

        @DAO
        public interface OperLogDAO {

            @SQL("select id, user_name, resource_pattern, resource_id, success, remarks, create_time from oper_log")
            public List&lt;OperLog&gt; query();

            @ReturnGeneratedKeys
            @SQL("insert into oper_log (user_name, resource_pattern, resource_id, success, remarks) values (:1.userName, :1.resourcePatterh, :1.resourceId, :1.success, :1.remarks)")
            public long insert(OperLog operLog);
        }
    </code>
</pre>
<h3>0.7.3 RemarkDAO.java</h3>
<pre>    <code>
        package com.qd.lib.dao;

        import net.paoding.rose.jade.annotation.DAO;
        import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
        import net.paoding.rose.jade.annotation.SQL;

        import java.util.List;

        import com.qd.lib.model.Remark;

        @DAO
        public interface RemarkDAO {

            // 标注一个@SQL，写入你的sql语句
            // <strong>不能写select * from remark，这样的后果可能会因为数据库增加了一个字段，但Remark没有相应字段的属性，Jade将抛出异常</strong>
            // <strong>参数以冒号开始，:1表示第一个参数</strong>
            @SQL("select id, user_name, book_id, essay, create_time from remark where book_id=:1")
            public List&lt;Remark&gt; queryByBook(long bookId);

            @SQL("delete from remark where book_id=:1")
            public void deleteByBook(long bookId);

            // 返回int表示影响的条数，就这个示例而言，应该就是返回1
            @SQL("delete from remark where id=:1")
            public int delete(long remarkId);

            // <strong>:1.userName表示第一个参数的userName属性</strong>
            @ReturnGeneratedKeys
            @SQL("insert into remark (user_name, book_id, essay) values (:1.userName, :1.bookId, :1.essay)")
            public long insert(Remark remark);
        }
    </code>
</pre>
<h3>0.7.4 UserDAO.java</h3>
<pre>    <code>
        package com.qd.lib.dao;

        import net.paoding.rose.jade.annotation.DAO;
        import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
        import net.paoding.rose.jade.annotation.SQL;

        import java.util.List;

        import com.qd.lib.model.User;

        @DAO
        public interface UserDAO {

            @SQL("select id, name, login_name from user where login_name=:1")
            public User getByLoginName(String loginName);

            @SQL("select id, name, password, login_name, create_time from user where id=:1")
            public User get(long userId);

            @SQL("select id, name, login_name, create_time from user")
            public List&lt;User&gt; query();

            @SQL("delete from user where id=:1")
            public void delete(long userId);

            @ReturnGeneratedKeys
            @SQL("insert into user (name, password, login_name, create_time) values (:1.name, :1.password, :1.loginName, 1.createTime")
            public long insert(User user);
        }
    </code>
</pre>
<h2>0.8 Service设计</h2>
<h3>0.8.1 BookServiceImpl.java</h3>
<pre>    <code>
        package com.qd.lib.service.impl;

        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import com.qd.lib.dao.BookDAO;
        import com.qd.lib.model.Book;
        import com.qd.lib.service.BookService;

        @Service
        public class BookServiceImpl implements BookService {

            @Autowired
            private BookDAO bookDAO;

            @Override
            public Book get(long bookId) {
                return bookDAO.get(bookId);
            }

            @Override
            public List&lt;Book&gt; find(int limit) {
                return bookDAO.query(limit);
            }

            @Override
            public List&lt;Book&gt; find(long bookId, int limit) {
                return bookDAO.query(bookId, limit);
            }

            @Override
            public void modify(Book book) {
                bookDAO.update(book);
            }

            @Override
            public long save(Book book) {
                return bookDAO.insert(book);
            }
        }
    </code>
</pre>
<h3>0.8.2 OperLogServiceImpl.java</h3>
<pre>    <code>
        package com.qd.lib.service.impl;

        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import com.qd.lib.dao.OperLogDAO;
        import com.qd.lib.model.OperLog;
        import com.qd.lib.service.OperLogService;

        @Service
        public class OperLogServiceImpl implements OperLogService {

            @Autowired
            private OperLogDAO operLogDAO;

            @Override
            public List&lt;OperLog&gt; find() {
                return operLogDAO.query();
            }

            @Override
            public long save(OperLog operLog) {
                return operLogDAO.insert(operLog);
            }
        }
    </code>
</pre>
<h3>0.8.3 RemarkServiceImpl.java</h3>
<pre>    <code>
        package com.qd.lib.service.impl;

        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import com.qd.lib.dao.RemarkDAO;
        import com.qd.lib.model.Remark;
        import com.qd.lib.service.RemarkService;

        @Service
        public class RemarkServiceImpl implements RemarkService {

            @Autowired
            private RemarkDAO remarkDAO;

            @Override
            public List&lt;Remark&gt; findByBook(long bookId) {
                return remarkDAO.queryByBook(bookId);
            }

            @Override
            public void removeByBook(long bookId) {
                remarkDAO.deleteByBook(bookId);
            }

            @Override
            public int remove(long remarkId) {
                return remarkDAO.delete(remarkId);
            }

            @Override
            public long save(Remark remark) {
                return remarkDAO.insert(remark);
            }
        }
    </code>
</pre>
<h3>0.8.4 UserServiceImpl.java</h3>
<pre>    <code>
        package com.qd.lib.service.impl;

        import java.util.List;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import com.qd.lib.dao.UserDAO;
        import com.qd.lib.model.User;
        import com.qd.lib.service.UserService;

        @Service
        public class UserServiceImpl implements UserService {

            @Autowired
            private UserDAO userDAO;

            @Override
            public User getByLoginName(String loginName) {
                return userDAO.getByLoginName(loginName);
            }

            @Override
            public User get(long userId) {
                return userDAO.get(userId);
            }

            @Override
            public List&lt;User&gt; find() {
                return userDAO.query();
            }

            @Override
            public void remove(long userId) {
                userDAO.delete(userId);
            }

            @Override
            public long save(User user) {
                return userDAO.insert(user);
            }
        }
    </code>
</pre>
<h2>0.9 资源配置</h2>
<h3>0.9.1 数据源配置applicationContext-datasource.xml</h3>
<pre>    <code>
        &lt;context:property-placeholder location="classpath:db.properties" /&gt;

        &lt;!-- com.qd包对应数据源配置 dbcp --&gt;
        &lt;bean id="jade.dataSource.com.qd" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;
            &lt;property name="driverClassName" value="${driver.class}" /&gt;
            &lt;property name="url" value="${jdbc.url}" /&gt;
            &lt;property name="username" value="${user.name}" /&gt;
            &lt;property name="password" value="${user.pwd}" /&gt;
            &lt;!-- 运行判断连接超时任务的时间间隔，单位为毫秒，默认为-1，即不执行任务。 --&gt;
            &lt;property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /&gt;
            &lt;!-- 连接的超时时间，默认为半小时。 --&gt;
            &lt;property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /&gt;
        &lt;/bean&gt;
    </code>
</pre>
<h3>0.9.2 执行器配置applicationContext-executor.xml</h3>
<pre>    <code>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
               default-lazy-init="true"&gt;
            &lt;bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" destroy-method="shutdown" /&gt;
        &lt;/beans&gt;
    </code>
</pre>
<h2>0.10 视图设计</h2>
<ul>
    <li><strong>rose默认：必须在WEB应用根目录下创建views目录,也就是views目录要和WEB-INF同级别，所有视图文件必须放在views目录下，这个不能错了，否则会404；</strong></li>
    <li><strong>rose默认：controllers包下的控制器对应的视图仓库是views，controllers.book、controllers.user、controllers.logs对应的视图仓库是views/book、views/user、views/logs，即与他们相对于controllers的路径一样；</strong></li>
    <li><strong>无论使用jsp还是vm，控制器返回都不需要带后缀，rose会判断实际views目录中存在的是jsp或vm等(极端地，如果2个文件都存在，则以字母顺序优先，即jsp优先于vm)(rose会做缓存的，而非总是通过转动磁盘来判断)；</strong></li>
    <li><strong>如果控制器返回字符串不以"/"开头，但包含"/"，rose则会在views文件夹下，按照返回的字符串路径，查找视图文件，如："page/book.jsp"，则会查找文件"/views/page/book.jsp"；</strong></li>
</ul>
<h1>第一章：入门指引</h1>
<h2>1.1 Rose简介</h2>
<h3>1.1.1 Rose是什么</h3>
<ul>
    <li>基于IoC容器 (使用Spring 2.5.6).</li>
    <li>收集最佳实践，形成规范和惯例，引导按规范惯例，简便开发.</li>
    <li>收集通用功能，形成一些可使用的组件，提高生产效率.</li>
    <li>特性的插拔，使用基于组合而非继承的设计.</li>
    <li>提供可扩展的点，保持框架的可扩展性.</li>
    <li>注重使用简易性的同时，注重内部代码设计和实现.</li>
</ul>
<h3>1.1.2 Rose能做什么</h3>

<ul>
    <li>初级rose用户：
        <ul>
            <li>rose可以用来完成一个网站。</li>
        </ul>
    </li>
    <li>中级rose用户：
        <ul>
            <li>
                rose可以用来完成一个大型网站，它提供的jade功能使得你的项目可以快速开发，自然切入连接池；它提供的portal功能，可以将一个网页分多个线程发起向DB的请求，节省用户的时间；它提供的pipe功能类似facebook的bigpipe，让前端加速，与此同时还有portal多线程的优势。
            </li>
        </ul>
    </li>
    <li>高级rose用户：
        <ul>
            <li>
                rose可以自由加入spring任何特性，比如定时执行（去TM的crontab）；比如拦截器做统一权限控制。可以自由配置主库从库，分表规则。配置thrift、zookeeper可以得到牛B的高可用性高性能服务集群。
            </li>
        </ul>
    </li>
</ul>
<h2>1.2 简明教程</h2>

<h3>1.2.1 需要些什么</h3>
<ul>
    <li>提前学习什么是maven：简单地说，maven是个build工具，用一个pom.xml来定义项目的依赖。通过一个命令mvn，可以自由地build compile（知道make吧，类似，或者类似ant）。</li>
    <li>也许还需要一个自己的Maven仓库，如果没有，可以使用开源中国Maven仓库：<a href="http://maven.oschina.net/home.html" target="_blank">http://maven.oschina.net/home.html</a>
    </li>
    <li>然后需要在你的项目的pom文件中添加：</li>
</ul>
<pre>    <code>
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-jade&lt;/artifactId&gt;
            &lt;version&gt;1.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-scanning&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-portal&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
    </code>
</pre>
<h1>第二章：配置与使用</h1>

<h2>2.1 点火：基础的pom文件</h2>
<pre>    <code>
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-jade&lt;/artifactId&gt;
            &lt;version&gt;1.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-scanning&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-portal&lt;/artifactId&gt;
            &lt;version&gt;1.0&lt;/version&gt;
        &lt;/dependency&gt;
    </code>
</pre>
<p>上述是rose环境最基础的依赖包。再添加一点常见的编译设置：</p>
<pre>    <code>
        &lt;build&gt;
            &lt;resources&gt;
                &lt;resource&gt;
                    &lt;directory&gt;src/main/resources&lt;/directory&gt;
                    &lt;includes&gt;
                        &lt;include&gt;**/*.*&lt;/include&gt;
                    &lt;/includes&gt;
                &lt;/resource&gt;
            &lt;/resources&gt;
            &lt;plugins&gt;
                &lt;plugin&gt;
                    &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                    &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
                    &lt;version&gt;2.0.2&lt;/version&gt;
                    &lt;configuration&gt;
                        &lt;webResources&gt;
                            &lt;resource&gt;
                                &lt;targetPath&gt;WEB-INF&lt;/targetPath&gt;
                                &lt;filtering&gt;true&lt;/filtering&gt;
                                &lt;directory&gt;src/main/resources&lt;/directory&gt;
                                &lt;includes&gt;
                                    &lt;include&gt;**/*.xml&lt;/include&gt;
                                    &lt;include&gt;**/*.properties&lt;/include&gt;
                                &lt;/includes&gt;
                                &lt;targetPath&gt;WEB-INF&lt;/targetPath&gt;
                            &lt;/resource&gt;
                        &lt;/webResources&gt;
                    &lt;/configuration&gt;
                &lt;/plugin&gt;
                &lt;plugin&gt;
                    &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                    &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
                    &lt;configuration&gt;
                        &lt;source&gt;1.6&lt;/source&gt;
                        &lt;target&gt;1.6&lt;/target&gt;
                        &lt;fork&gt;true&lt;/fork&gt;
                        &lt;verbose&gt;true&lt;/verbose&gt;
                        &lt;encoding&gt;UTF-8&lt;/encoding&gt;
                        &lt;compilerArguments&gt;
                            &lt;sourcepath&gt;
                                /src/main/java
                            &lt;/sourcepath&gt;
                        &lt;/compilerArguments&gt;
                    &lt;/configuration&gt;
                &lt;/plugin&gt;
                &lt;plugin&gt;
                    &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                    &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
                    &lt;configuration&gt;
                        &lt;!-- 忽略测试 --&gt;
                        &lt;skip&gt;true&lt;/skip&gt;
                    &lt;/configuration&gt;
                &lt;/plugin&gt;
            &lt;/plugins&gt;
        &lt;/build&gt;
    </code>
</pre>
<p>上述是编译设置，也是放在project段落里。</p>

<h2>2.2 松离合：必不可少的web.xml</h2>

<p>在src/main/webapp/WEB-INF文件夹下建立web.xml:</p>
<pre>    <code>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
            id="WebApp_ID" version="2.5"&gt;
            &lt;display-name&gt;rose-example&lt;/display-name&gt;
            &lt;welcome-file-list&gt;
                &lt;welcome-file&gt;index.html&lt;/welcome-file&gt;
                &lt;welcome-file&gt;index.htm&lt;/welcome-file&gt;
                &lt;welcome-file&gt;index.jsp&lt;/welcome-file&gt;
                &lt;welcome-file&gt;default.html&lt;/welcome-file&gt;
                &lt;welcome-file&gt;default.htm&lt;/welcome-file&gt;
                &lt;welcome-file&gt;default.jsp&lt;/welcome-file&gt;
            &lt;/welcome-file-list&gt;
            &lt;!--
                - Rose内部使用apache的commons-logging作为日志输出接口，故Rose并没有强制使用log4j。
                - 但实践上您可能会选择使用log4j，如果如此的话您可以在web.xml配置以下内容，同时按照下面的说明，配置log4j.xml
                - 这个参数告诉Log4jConfigListener Log4J的属性文件位置。
                - 在开发、测试环境下，建议把net.paoding和org.springframework配置成DEBUG模式。
            --&gt;
            &lt;context-param&gt;
                &lt;param-name&gt;log4jConfigLocation&lt;/param-name&gt;
                &lt;param-value&gt;classpath:log4j.xml&lt;/param-value&gt;
            &lt;/context-param&gt;
            &lt;!--
                - 这个就是Log4jConfigListener配置，它可以读取上面配置的log4jConfigLocation等信息，
                - 配置Log4J信息以及进行webapp根地址的暴露(暴露使其成为一个System属性，请搜索
                - log4jExposeWebAppRoot了解相关说明)
            --&gt;
            &lt;listener&gt;
                &lt;listener-class&gt;org.springframework.web.util.Log4jConfigListener&lt;/listener-class&gt;
            &lt;/listener&gt;
            &lt;!--
                - RoseFilter是Rose的最主要配置,也是Rose框架本身的唯一配置了，大多少情况下，按照Rose提供web.xml中配置方式拷贝过来即可，不需要修改。
                - 默认情况下RoseFilter会过滤所有的请求，对请求进行识别，对应该由Controller处理的进行分派，不应该由Controller
                - 处理的则让它简单通过，通往它该去的地方。
                - 这里最重要的2点就是：
                -   1)要保持dispatcher含有FORWARD，INCLUDE;
                -   2)要保证filter-mapping是所有filter-mapping的最后一个
            --&gt;
            &lt;filter&gt;
                &lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
                &lt;filter-class&gt;net.paoding.rose.RoseFilter&lt;/filter-class&gt;
            &lt;/filter&gt;
            &lt;filter-mapping&gt;
                &lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
                &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
                &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
                &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
                &lt;dispatcher&gt;INCLUDE&lt;/dispatcher&gt;
            &lt;/filter-mapping&gt;
        &lt;/web-app&gt;
    </code>
</pre>
<h2>2.3 踩油门：applicationContext.xml</h2>

<p>src/main/resources/applicationContext.xml是spring环境的油门，所有包的扫描和启动都在这里定义：</p>
<pre>    <code>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-2.5.xsd"
            default-lazy-init="true"&gt;
            <!-- 自动扫描 -->
            &lt;context:annotation-config /&gt;
            &lt;context:component-scan base-package="com.qd" /&gt;
        &lt;/beans&gt;
    </code>
</pre>
<h2>2.4 hello world</h2>

<ul>
    <li>在src/main/java上右键 -&gt; new -&gt; package -&gt; name: com.qd</li>
    <li>在com.chen上右键 -&gt; new -&gt; package -&gt; com.qd.controllers [controllers是rose框架默认的加载controller的package
        name]
    </li>
    <li>在com.qd.controllers上右键 -&gt; new -&gt; class -&gt; HelloController
        [*Controller是rose框架默认的controller层的class后缀]
    </li>
    <li>打开HelloController这个类</li>
    <li>在public class HelloController添加注解@Path("") [Path注解是rose框架提供的标识每个controller的对外访问时的基础路径]</li>
    <li>在HelloController中添加方法</li>
</ul>
<pre>    <code>
        package com.qd.controllers;

        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Get;

        @Path("hello")
        public class HelloController {

            @Get("world")
            public String index() {
                return "@hello world";
            }
        }
    </code>
</pre>
<ul>
    <li>[Get注解是rose框架提供的标识一个http访问是get还是post或者是其他，并且会将path与get中的字符串连接成一个url]</li>
    <li>上述代码可以从浏览器访问：<a href="http://localhost:8080/hello/world" target="_blank">http://localhost:8080/hello/world</a>
        [注意path与get中的参数]。
    </li>
</ul>
<h2>2.5 如何在已有的web应用中使用rose</h2>

<h3>2.5.1 Spring集成问题</h3>

<p>如果您在web项目中使用了Spring容器(不限制Spring
    MVC)，您一定在web.xml配置了ContextLoaderListener或ContextLoaderServlet，通常您还会配置一个名为contextConfigLocation的context-param表示Spring容器的地址，假如就是这样的话，为了在已有的Spring
    MVC或Struts等框架使用Rose，你需要这样改造：</p>
<ul>
    <li>
        从web.xml中去掉ContextLoaderListener、ContextLoaderServlet，RoseFilter会充当ContextLoader功能，把Spring环境生成好，按照Spring的规范放好，使得Spring
        MVC或Struts可以用到；
    </li>
    <li>去掉contextConfigLocation配置，Rose默认会读取WEB-INF、WEB-INF/classes下的applicationContext开头的xml文件，把他们视为Spring配置文件；</li>
    <li>
        如果原来您使用的spring配置文件不是applicationContext开始的，请更改过来即可，文件命名可以是applicationContext.xml、applicationContext-amodule.xml等；
    </li>
    <li>如果原来您使用的配置文件并不直接在WEB-INF或WEB-INF/classes下的(比方是classpath:/spring/*.xml)，那么您可以有两种选择：
        <ul>
            <li>1. 把classpath:/spring下的配置文件改到WEB-INF或WEB-INF/classes下，并命名为以applicationContext开始的xml文件；</li>
            <li>2.
                保持contextConfigLocation配置为classpath:/spring/*.xml，此时Rose还会读取WEB-INF/classes下的applicationContext*.xml，但不会再读取WEB-INF下的applicationContext*.xml，除非你设置contextConfigLocation为classpath:/spring/*.xml,/WEB-INF/applicationContext*.xml；
            </li>
        </ul>
    </li>
    <li>
        总之，无论如何Rose总是会读取WEB-INF/classes下的，也就是说：如果你没有设置contextConfigLocation，Rose还会自动读取WEB-INF/classes的。如果你设置了contextConfigLocation，则Rose也将读取WEB-INF/classes下的，所以为了不多次重复读取WEB-INF/classes下的配置，contextConfigLocation一定不要再设置classpath:applicationContext*.xml之类的配置，您需要把classpath:applicationContext*.xml的去掉，其他的保留)；
    </li>
</ul>
<h3>2.5.2 RoseFilter问题</h3>
<ul>
    <li>首先只要做好以上Spring的工作，RoseFilter是可以和任何框架共存的</li>
    <li>RoseFilter在配置上，一般要是所有Filter的最后一个，已确认Rose的请求能够走完web.xml中的所有Filter；</li>
    <li>struts2下，roseFilter则不要是最后一个，要在struts2的filter之前，这样才能保证rose优先匹配；</li>
    <li>RoseFilter在web.xml的配置，一定要按照RoseFilter.java
        javadoc注释说明的那样配置，既配置url-pattern为/*，请不要配置*.do、*.abc之类的，这样配置对整个web来说并没有提高性能，而且可能导致错误。即使您真的只有/admin采用rose开发,也不要配置为/admin/*之类的配置，这对rose的有效使用是不利的[长话以后再说，这里先亮结论]。总之，rose有优秀匹配算法，配置成/*不会浪费性能；
    </li>
    <li>当一个请求确定是Rose应该处理的，那么他就不会在分发给Servlet处理了。这样，就做到了Rose和其他Servlet框架共存了，而且是Rose优先的；</li>
</ul>
<h1>第三章：框架功能</h1>

<h2>3.1 controller层：url对照规则与返回结果规则</h2>

<h3>3.1.1 url对照规则——最简单的例子</h3>

<p>先看看怎样把url和某个方法对应起来。为了方便说明，现在我们来一起完成一个极简版的贴吧。 </p>
<h4>1) 贴吧需要什么功能 </h4>

<p>贴吧中当然会有很多“主帖”（topic），“主帖”下会有很多“跟帖”（comment）。<br>
    一般，贴吧中最基本的，会有下面这几个功能需要我们完成： </p>

<ul>
    <li>显示主帖列表</li>
    <li>显示单个主帖和它的跟贴</li>
    <li>显示单个跟贴</li>
    <li>创建一个主帖</li>
    <li>创建一个跟贴</li>
</ul>
<h4>2) 设计 web API</h4>

<p>然后让我们来规划一个REST风格的 web API :（“GET”和“POST”是指HTTP1.1中的请求方法）
</p>

<ul>
    <li>显示主帖列表

        <ul>
            <li>GET <a href="http://localhost:8080/forum/topic" target="_blank">http://localhost:8080/forum/topic</a>
            </li>
        </ul>
    </li>
    <li>显示单个主帖和它的跟贴

        <ul>
            <li>GET <a href="http://localhost:8080/forum/topic/123" target="_blank">http://localhost:8080/forum/topic/123</a>
            </li>
        </ul>
    </li>
    <li>显示单个跟贴

        <ul>
            <li>GET <a href="http://localhost:8080/forum/topic/123/comment/456" target="_blank">http://localhost:8080/forum/topic/123/comment/456</a>
            </li>
        </ul>
    </li>
    <li>创建一个主帖

        <ul>
            <li>POST <a href="http://localhost:8080/forum/topic" target="_blank">http://localhost:8080/forum/topic</a>
            </li>
        </ul>
    </li>
    <li>创建一个跟贴

        <ul>
            <li>POST <a href="http://localhost:8080/forum/topic/123/comment" target="_blank">http://localhost:8080/forum/topic/123/comment</a>
            </li>
        </ul>
    </li>
</ul>
<p>可以发现一个共同点，所有API中，URI部分的第一级都是“/forum”（但这并不是规定，仅仅为了演示）。 </p>

<h4>3) 实现 web API</h4>

<p>首先新建一个类，这个类的类名必须以“Controller”结尾： </p>

<pre>    <code>
        package com.qd.controllers.forum;

        @Path("")
        public class ForumController {
        }
    </code>
</pre>
<p>
    注意标注在类(class)上的注解“@Path("")”、包路径，这意味着，这个类中定义的所有API的URI，都必须以“forum”开头，比如“/forum/xxx”和“/forum/yyy”等（但“forum”不一定是整个URI的第一级，比如“/aaa/forum/bbb”）。 </p>
<p>
    接着，实现第一个API——“GET <a href="http://localhost:8080/forum/topic" target="_blank">http://localhost:8080/forum/topic</a>
</p>

<pre>    <code>
        package com.qd.controllers.forum;

        @Path("")
        public class ForumController {
            @Get("topic")
            public String getTopics() {
                //显示主帖列表
                return "topiclist";
            }
        }
    </code>
</pre>

<p>
    因为是“GET”方法，所以在该方法上标注“@Get("topic")”，URI“/forum/topic”中的“forum”已经通过包路径中定义过了，所以只剩下“topic”，于是写“@Get("topic")”。 </p>
<p>
    再看第二个API——“GET <a href="http://localhost:8080/forum/topic/123" target="_blank">http://localhost:8080/forum/topic/123</a><br>
    跟前一个的唯一区别是，后面多了个“/123”，表示主帖id，而这个id当然不是固定的，只有用户点击链接发来请求时才能知道，肿么办？<br>
    没关系，rose支持正则表达式！可以这么写：
</p>

<pre>    <code>
        @Get("topic/{topicId:[0-9]+}")
        public String showTopic(@Param("topicId") int topicId) {
            //显示单个主帖和它的跟贴
            return "topic";
        }
    </code>
</pre>

<p>与前一个API相比，多了段“/{topicId:[0-9]+}”。正则表达式被大括号"{}"包围，格式为“{ paramName : regularExpression
    }”，只有请求的URI能被正则表达式匹配时，才会执行这个方法，而被匹配的值将被保存在名为“topicId”的参数中。 </p>

<p>同理，实现第三个API，稍微复杂一点： </p>

<pre>    <code>
        @Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
        public String showComment(@Param("topicId") int topicId, @Param("commentId") int commentId) {
            //显示单个跟贴
            return "comment";
        }
    </code>
</pre>

<p>最后两个API使用POST方法，其他与前面相同： </p>

<pre>    <code>
        @Post("topic")
        public String createTopic(){
            //创建一个主帖
            return "topic";
        }
        @Post("topic/{topicId:[0-9]+}/comment")
        public String createComment(@Param("topicId") int topicId){
            //创建一个跟贴
            return "comment";
        }
    </code>
</pre>

<p>完整的代码如下（省略了import语句）： </p>

<pre>    <code>
        package com.qd.controllers.forum;

        import net.paoding.rose.web.annotation.Param;
        import net.paoding.rose.web.annotation.Path;
        import net.paoding.rose.web.annotation.rest.Get;
        import net.paoding.rose.web.annotation.rest.Post;
        import net.paoding.rose.web.var.Model;

        @Path("")
        public class ForumController {

            @Get("topic")
            public String getTopics() {
                //显示主帖列表
                return "topiclist";
            }

            @Get("topic/{topicId:[0-9]+}")
            public String showTopic(Model model, @Param("topicId") int topicId) {
                //显示单个主帖和它的跟贴
                model.add("id", topicId);
                return "topic";
            }

            @Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
            public String showComment(Model model, @Param("topicId") int topicId, @Param("commentId") int commentId) {
                //显示单个跟贴
                model.add("topicId", topicId);
                model.add("commentId", commentId);
                return "comment";
            }

            @Post("topic")
            public String createTopic(){
                //创建一个主帖
                return "topic";
            }
            @Post("topic/{topicId:[0-9]+}/comment")
            public String createComment(Model model, @Param("topicId") int topicId){
                //创建一个跟贴
                model.add("topicId", topicId);
                return "comment";
            }
        }
    </code>
</pre>

<p>至此，一个贴吧功能的Controller就编写完成了。</p>

<h4>4) 更多细节</h4>

<p>除了上面例子中的做法（@Path("")，@Get("")和@Post("")），还可以通过包路径来规划URI。 </p>

<h3>3.1.2 返回结果规则</h3>
<h4>1) 渲染页面并返回</h4>

<p>web开发中最常规的做法是，运行Servlet中的方法，最后将渲染好的页面内容返回。下面说说rose是怎么做的。<br>
    上面的贴吧例子中，每个方法的返回值都是一个普通字符串，比如“comment”，意思是，找到web项目中“webapp/views”路径下名叫“comment”的视图文件，比如“comment.jsp”，用这个视图文件来渲染网页结果并返回。<br>
    comment.jsp的代码如下： </p>

<pre>    <code>
        ...
        &lt;body&gt;
            昵称：${name}&lt;br&gt;
            回复内容：${commentContent}
        &lt;/body&gt;
        ...
    </code>
</pre>

<p>页面中有两个变量——name和commentContent，变量的值是在java代码中设置的，如下： </p>

<pre>    <code>
        @Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
        public String showComment(Model model, @Param("topicId") int topicId, @Param("commentId") int commentId) {
            //显示单个跟贴
            model.add("name", "郭德纲");
            model.add("commentContent", "今天来人不少，我很欣慰啊！");
            return "comment";
        }
    </code>
</pre>

<p>总结一句话，通过rose提供类net.paoding.rose.web.var.Model来设置变量名和变量值，然后在视图文件中用“${paramName}”的方式得到变量值。<br>
    变量的值可以是String，boolean，数字，数组，对象(JavaBean)。 </p>

<p>如果是对象，使用方法如下： </p>

<pre>    <code>
        javaBean：
            public class Bean{
                private String beanValue;
                public String getBeanValue(){...}
                public String setBeanValue(String beanValue){...}
            }
        ==================
        controller中的方法：
            @Get("test")
            public String test(Model model) {
                Bean bean = new Bean();
                bean.setBeanValue("this_is_a_bean");
                model.add("mybean", bean);
                return "test";
            }
        ==================
        test.jsp文件：
            ...
            &lt;body&gt;
                bean里的值：${mybean.beanValue}
            &lt;/body&gt;
            ...
        ==================
        输出为：
        bean里的值：this_is_a_bean
    </code>
</pre>

<p>如果是个数组，可以结合JSTL对数组循环访问： </p>

<pre>    <code>
        controller中的方法：
            @Get("test")
            public String test(Model model) {
                String[] array = {"111","222","333"};
                model.add("array", array);
                return "test";
            }
        ==================
        test.jsp文件：
            ...
            &lt;body&gt;
                &lt;c:forEach var="item" items="${array}" varStatus="status"&gt;
                    打印：${item}&lt;br&gt;
                &lt;/c:forEach&gt;
            &lt;/body&gt;
            ...
        ==================
        输出为：
            111
            222
            333
    </code>
</pre>

<h4>2) 还有几种规则？</h4>

<p>controller方法的返回值有下面几种规则：</p>

<ul>
    <li>
        1. 返回普通字符串，如上所述，最常用的做法，渲染视图文件并返回；<a href="http://localhost:8080/forum/topic" target="_blank">http://localhost:8080/forum/topic</a><br>
    </li>
    <li>
        2. 以“@”开头的字符串，比如“return "@HelloWorld";”，会将“@”后面的字符串“HelloWorld”作为结果返回；<a href="http://localhost:8080/hello/world" target="_blank">http://localhost:8080/hello/world</a><br>
    </li>
    <li>
        3. "@json:xxxx"、"@application/json:xxxx"、"@application/x-json:xxxx"(字节流)表示输出content-type为application/x-json的xxx字节流到客户端；<a href="http://localhost:8080/hello/json" target="_blank">http://localhost:8080/hello/json</a><br>
    </li>
    <li>
        4. "@xml:xxxx"、"@application/xml:xxxx"、"@application/x-xml:xxxx"(字节流)表示输出content-type为application/x-xml的xxxx字节流到客户端；<a href="http://localhost:8080/hello/xml" target="_blank">http://localhost:8080/hello/xml</a><br>
    </li>
    <li>
        5. "@plain:xxxx"、"@text:xxxxx"、"@text/plain:xxxxx" 表示输出content-type为text/plain的字符串到客户端；<a href="http://localhost:8080/hello/plain" target="_blank">http://localhost:8080/hello/plain</a><br>
    </li>
    <li>
        6. "@xxxxx"、"@html:xxxx"、"@text/html:xxxxx" 表示输出content-type为text/html的字符串到客户端；<a href="http://localhost:8080/hello/html" target="_blank">http://localhost:8080/hello/html</a><br>
    </li>
    <li>
        7. <strong>Rose默认使用UTF-8编码，</strong>对于如：@json、@xml、@plain等上述输出中包含中文时，未做Response中文编码设置，所以需要进行response.setCharacterEncoding("UTF-8")响应编码的设置；<a href="http://localhost:8080/hello/utf8" target="_blank">http://localhost:8080/hello/utf8</a><br>
    </li>
</ul>
<p>
    我们支持以下Content-Type：json、xml、html、plain、text、所有以text/开始的，比如text/html、text/plain、text/javascript等；所有以application/开头的，比如applicaton/x-json等，其他的概不支持。
</p>
<pre>    <code>
        @Get("json")
        public String json() {
            Gson gson = new Gson();

            Map&lt;String, String&gt; jsonMap = new HashMap&lt;String, String&gt;();
            jsonMap.put("1", "1");
            jsonMap.put("2", "2");
            jsonMap.put("3", "3");

            return "@json:"+gson.toJson(jsonMap);
            //		return "@application/json:"+gson.toJson(jsonMap);
            //		return "@application/x-json:"+gson.toJson(jsonMap);
        }

        @Get("xml")
        public String xml() {

            return "@xml:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            "&lt;user&gt;\n" +
            "    &lt;id&gt;Tao&lt;/id&gt;\n" +
            "    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            "&lt;/user&gt;";

            //		return "@application/xml:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            //				"&lt;user&gt;\n" +
            //				"    &lt;id&gt;Tao&lt;/id&gt;\n" +
            //				"    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            //				"&lt;/user&gt;";
            //
            //		return "@application/x-xml:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            //				"&lt;user&gt;\n" +
            //				"    &lt;id&gt;Tao&lt;/id&gt;\n" +
            //				"    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            //				"&lt;/user&gt;";
        }

        @Get("plain")
        public String plain() {
            return "@plain:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            "&lt;user&gt;\n" +
            "    &lt;id&gt;Tao&lt;/id&gt;\n" +
            "    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            "&lt;/user&gt;";

            //		return "@text:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            //				"&lt;user&gt;\n" +
            //				"    &lt;id&gt;Tao&lt;/id&gt;\n" +
            //				"    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            //				"&lt;/user&gt;";

            //		return "@text/plain:&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n" +
            //				"&lt;user&gt;\n" +
            //				"    &lt;id&gt;Tao&lt;/id&gt;\n" +
            //				"    &lt;name&gt;Mingkai&lt;/name&gt;\n" +
            //				"&lt;/user&gt;";
        }

        @Get("html")
        public String html() {
            return "@html:1&lt;br&gt;2&lt;br&gt;3";
            //		return "@text/html:1&lt;br&gt;2&lt;br&gt;3";
        }

        @Get("utf8")
        public String utf8(Invocation inv) {
            Gson gson = new Gson();

            Map&lt;String, String&gt; jsonMap = new HashMap&lt;String, String&gt;();
            jsonMap.put("1", "陶");
            jsonMap.put("2", "明");
            jsonMap.put("3", "凯");

            inv.getResponse().setCharacterEncoding("UTF-8");
            return "@json:" + gson.toJson(jsonMap);
        }
    </code>
</pre>
<p>
    4.【不推荐使用】以“r:”开头的字符串，比如“return "r:/aaa";”，等效于调用“javax.servlet.http.HttpServletResponse.sendRedirect("/aaa")”，将执行301跳转。<a href="http://localhost:8080/hello/redirect" target="_blank">http://localhost:8080/hello/redirect</a><br>
</p>
<pre>    <code>
        @Get("redirect")
        public String redirect() {
            return "r:/hello/world";
        }
    </code>
</pre>
<p>
    5.【不推荐使用】以“f:”开头的字符串，比如“return "f:/bbb";”，将会携带参数再次匹配roseTree，找到controller中某个方法并执行，相当于“javax.servlet.RequestDispatcher.forward(request, response)”。<a href="http://localhost:8080/hello/forward" target="_blank">http://localhost:8080/hello/forward</a>
</p>
<pre>    <code>
        @Get("forward")
        public String forward() {
            return "f:/hello/world";
        }
    </code>
</pre>

<h4>3) 做一个标示</h4>

<p>目前来说这个步骤是可选的，但是如果您是通过打jar来部署的，则这个步骤不可避免。</p>

<p>
    在src下建立创建META-INF/rose.properties文件，写上rose=controllers, applicationContext, dao, messages
    这个标识指示rose要识别你开发的jar包中的web代码、jar根目录下的applicationContext*.xml、messages*.xml或messages*.properties，以及jar包中的Jade DAO对象。
</p>
<pre>    <code>
        rose=controllers, applicationContext, dao, messages
    </code>
</pre>
<h3>3.1.3 原理</h3>

<p>Rose 是一个基于Servlet规范、Spring“规范”的WEB开发框架。 </p>

<p>Rose 框架通过在web.xml配置过滤器拦截并处理匹配的web请求，如果一个请求应该由在Rose框架下的类来处理， 该请求将在Rose调用中完成对客户端响应.
    如果一个请求在Rose中没有找到合适的类来为他服务，Rose将把该请求移交给web容器的其他组件来处理。 </p>

<p>Rose使用过滤器而非Servlet来接收web请求，这有它的合理性以及好处。 </p>

<p>Servlet规范以“边走边看”的方式来处理请求， 当服务器接收到一个web请求时，并没有要求在web.xml必须有相应的Servlet组件时才能处理，web请求被一系列Filter过滤时，
    Filter可以拿到相应的Request和Response对象 ，当Filter认为自己已经能够完成整个处理，它将不再调用chain.doNext()来使链中下个组件(Filter、Servlet、JSP)进行处理。 </p>

<p>使用过滤器的好处是，Rose可以很好地和其他web框架兼容。这在改造遗留系统、对各种uri的支持具有天然优越性。正是使用过滤器，Rose不再要求请求地址具有特殊的后缀。 </p>

<p>为了更好地理解，可以把Rose看成这样一种特殊的Servlet：它能够优先处理认定的事情，如无法处理再交给其它Filter、Servlet或JSP来处理。这个刚好是普通Servlet无法做到的 ： 如果一个请求以后缀名配置给他处理时候
    ，一旦该Servlet处理不了，Servlet规范没有提供机制使得可以由配置在web.xml的其他正常组件处理 (除404，500等错误处理组件之外)。 </p>

<p>一个web.xml中可能具有不只一个的Filter，Filter的先后顺序对系统具有重要影响，特别的，Rose自己的过滤器的配置顺序更是需要讲究
    。如果一个请求在被Rose处理前，还应该被其它一些过滤器过滤，请把这些过滤器的mapping配置在Rose过滤器之前。 </p>

<p>像前面提到过的，RoseFilter的配置，建议按以下配置即可： </p>

<div class="highlight">
<pre>        <span class="nt">&lt;filter&gt;</span>
                <span class="nt">&lt;filter-name&gt;</span>roseFilter<span class="nt">&lt;/filter-name&gt;</span>
                <span class="nt">&lt;filter-class&gt;</span>net.paoding.rose.RoseFilter<span class="nt">&lt;/filter-class&gt;</span>
        <span class="nt">&lt;/filter&gt;</span>
        <span class="nt">&lt;filter-mapping&gt;</span>
                <span class="nt">&lt;filter-name&gt;</span>roseFilter<span class="nt">&lt;/filter-name&gt;</span>
                <span class="nt">&lt;url-pattern&gt;</span>/*<span class="nt">&lt;/url-pattern&gt;</span>
                <span class="nt">&lt;dispatcher&gt;</span>REQUEST<span class="nt">&lt;/dispatcher&gt;</span>
                <span class="nt">&lt;dispatcher&gt;</span>FORWARD<span class="nt">&lt;/dispatcher&gt;</span>
                <span class="nt">&lt;dispatcher&gt;</span>INCLUDE<span class="nt">&lt;/dispatcher&gt;</span>
        <span class="nt">&lt;/filter-mapping&gt;</span>
</pre>
</div>


<p>大多数请况下，filter-mapping 应配置在所有Filter Mapping的最后。 不能将 FORWARD、INCLUDE 的 dispatcher 去掉，否则forward、
    include的请求Rose框架将拦截不到。 </p>

<p>Rose框架内部采用"匹配-&gt;执行"两阶段逻辑。Rose内部结构具有一个匹配树， 这个数据结构可以快速判断一个请求是否应该由Rose处理并进行， 没有找到匹配的请求交给过滤器的下一个组件处理。匹配成功的请求将进入”执行“阶段。
    执行阶段需要经过6个步骤处理：“参数解析 -〉 验证器 -〉 拦截器 -〉 控制器 -〉 视图渲染 -〉渲染后"的处理链。 </p>

<p>匹配树: 匹配树是一个多叉树，下面是一个例子： </p>

<pre>    <code>
        /
        GET="HomeController#index" package="com.qd.xxx.controllers"

        /about
        GET="HomeController#about" package="com.qd.xxx.controllers"

        /book
        GET="BookController#list" package="com.qd.xxx.controllers.sub"
        POST="BookController#add" package="com.qd.xxx.controllers.sub"
        /book/
        /book/{id}
        GET="BookController#show" package="com.qd.xxx.controllers.sub"

        /help
        GET="HomeController#help" package="com.qd.xxx.controllers"
    </code>
</pre>

<p>/代表这是一个根地址，也就是 http://localhost/ 代表的地址； </p>

<p>/的下级有个GET结点，代表对该地址支持GET访问，不支持POST等其它访问，如果进行POST访问将以405错误回应。 </p>

<p>/book代表这是一个/book地址，也就是 http://localhost/book 代表的地址； </p>

<p>/book下级有GET、POST两个结点，说明它支持GET和POST方法，根据HTTP语义，GET代表浏览，POST代表追加(向一个集合中追加一个条目)。 </p>

<p>/book下还有/book/地址，这个地址有点特别，它以'/'结尾，但实际它不会被任何地址访问到，rose对http://localhost/book/的处理会将它等价于 http://localhost/book 。 </p>

<p>这个特别的地址的存在完全是匹配树结构所需导致的，但不对实际匹配有任何坏的影响，所以也没有任何GET、POST等子结点。 </p>

<p>/book/{id}代表是一个/book/123456、/book/654321这样的地址，当然这可以支持正则表达式的。 </p>

<p>
    大部分情况下，匹配树的结构和实际的URI结构会一致，也因此匹配树的深度并不固定，每一个中间结点或叶子节点都有可能代表一个最终的URI地址，可以处理GET、POST等请求。对于那些匹配树存在的地址，但没有GET、POST、DELETE等子结点的，一旦用户请求了该地址，rose将直接把该请求转交给web容器处理，如果容器也不能处理它，最终用户将得到404响应。 </p>

<p><strong>目前浏览器不能支持的PUT、DELETE两个方法，您有两种选择：</strong></p>

<p>选择一：不使用PUT、DELETE：在这种选择下，建议您改为POST，但需要在URI增加一级/update、/delete</p>

<p>选择二：使用PUT、DELETE：在这种选择下，则请求需要以POST提交，并且在请求URL后增加一个：key为_method，value为PUT或DELETE，如：/update?_method=PUT</p>

<p><strong>匹配过程：</strong>
    Rose以请求的地址作为处理输入(不包含Query串，即问号后的字符串)。如果匹配树中存在对应的地址，且含有对应请求方法(GET、POST、PUT、DELETE)的，则表示匹配成功；如果含有其他方法的，但没有当前方法的（比如只支持GET，但当前是POST的），则也表示匹配成功，但最后会以405响应出去；如果所给的地址没有任何支持的方法或者没有找到匹配地址的，则表示匹配失败。1.0.1不支持回朔算法，1.0.2将支持部分回朔算法(待发布时再做详细介绍)。
</p>

<p><strong>参数解析：</strong>在调用验证器、拦截器 控制器之前，Rose完成2个解析：解析匹配树上动态的参数出实际值，解析控制器方法中参数实际的值。参数可能会解析失败(例如转化异常等等
    )，此时该参数以默认值进行代替，同时Rose解析失败和异常记录起来放到专门的类中，继续下一个过程而不打断执行。 </p>

<p><strong>拦截器：</strong>Rose使用自定义的拦截器接口而非一般的拦截器接口这是有理由的。使用Rose自定义的拦截器接口可以更容易地操作、控制Rose拦截。
    所谓拦截即是对已经匹配的控制器调用进行拦截，在其调用之前、之后以及页面渲染之后执行某些逻辑。设计良好的拦截器可以被多个控制器使用。</p>

<p><strong>Rose和Spring关系：</strong></p>

<p>
    作为一名现代的Java开发者，几乎是离不开Spring的。Spring是SpringSource.org公司提供的开源Java基础架构产品。我们并不完全迷信Spring，但从技术上讲Spring提供了很好的在web开发中使用的基础组件、基础容器，使得它得到我们很大的尊重。
</p>

<p>
    因为Spring充分地使用面向对象原则开发使Spring的丰富基础组件具有良好的使用和复用，有关web的一些基础组件包括：参数对象绑定器、Multipart对象抽象、视图渲染器等。这些组件被 Spring 自己的
    Spring-MVC 框架自己使用，同时也被 Rose 大量使用。
</p>

<p>
    除了对spring基础组件的使用，实际最重要的是Rose极大重视了对Spring
    ApplicationContext的使用。因为ApplicationContext的强大、聪慧，使得Rose的架构变得轻松而简单。每种框架对对象都有它的管理策略，Rose从本质上就是将各种各样的对象放置在合适的ApplicationContext上，不同的ApplicationContext之间形成上下级关系，让Spring来管理，并和开发者自定义的ApplicationContext有机结合起来。所以，从Spring的角度出发，开发者可以把Rose看出成是Spring的另外一种配置方式。也因此，Spring的@Autowired
    可在Rose的类中大量被使用。
</p>

<p>
    当然，为了更好地完成WEB工作以及极大的贯彻约定，不在没有意义的多种选择中彷徨，我们提供了Spring所没有的(或不同的)、Rose特有的各种机制：URI映射机制、参数映射机制以及各种道具的关系假设(拦截器、错误处理器、参数解析器)、页面映射关系、国际化配置机制等等。
</p>

<p><strong>Rose与类反射：</strong></p>

<p>
    使用Rose进行WEB开发，第一个程序可能就是在某个controllers
    package下创建了某控制器类。Rose的控制器不需要什么接口或继承扩展什么类。同时控制器类的WEB动作方法几乎也是普通方法：其一：我们对方法名或方法参数没有强制规定；其二、一个控制器可以包括多少个WEB动作方法不做数量限制。
</p>

<p>
    在这种框架下，虽然我们 有办法
    通过在运行时自动进行代码增强包装成Command而不使用类反射来实现目的，虽然预见到放在我们口袋里的方案比直接使用类反射技术性能肯定有所提高，但目前我们仍没有这种做，Rose仍直接是使用类反射技术来访问开发者定义的方法。这样的决定来自我们的经验：目前类反射看起来并不影响到我们WEB应用的性能和响应速度，而且在现代Java程序大量使用类反射技术环境中，单纯“优化”Rose成效
    预计 有限。当然，在必要时，我们不会放过可能的一点点性能提供。
</p>

<p>
    所以，到现在为止，开发者定义的控制器和方法最终是由Rose通过method.invoke方法调用(对于代理对象的，使用Proxy来调用，少见)来处理web请求。这是唯一的在“服务”状态中必使用的类反射地方。
</p>

<p>
    为了提高Rose的服务性能，Rose在启动的时候，已经把所有的控制器类和WEB动作方法进行了“解读”。在这个阶段，Rose会把所有URI映射规则以及尽可能做的事事先做好(包括对method方法名以及相关要素的缓存)，使Rose在服务状态时，不用再通过类反射来做和类、方法有关的识别和判断，直接就可以从缓存里获取method对象以及相关的其他要素进行处理，相信这已经是类反射API的极致追求。
</p>

<p>
    另外一个使用类反射的地方是，在控制器声明Bean参数的(表单对象)，这将促使Rose使用类反射技术，把或请求对象中获取的参数进行转化动态设置到Bean的属性中。
</p>

<h2>3.2 controller层：进阶</h2>

<h3>3.2.1 起步</h3>

<p><a href="http://localhost:8080/user/test" target="_blank">http://localhost:8080/user/test</a></p>
<pre>    <code>
        // 控制器必须声明在controllers或其子package下，注意controllers后面有个s
        package com.qd.controllers;

        // 控制器要以Controller结尾，可不继承或实现其他类或接口
        public class UserController {

            public String test() {
                // 返回@开始的字符串，表示将紧跟@之后的字符串显示在页面上
                return "@" + new java.util.Date();
            }
        }
    </code>
</pre>
<h3>3.2.2 返回一个velocity页面</h3>

<p><a href="http://localhost:8080/user/velocity" target="_blank">http://localhost:8080/user/velocity</a></p>
<pre>    <code>
        public class UserController {

            public String velocity() {
                // 返回一个普通字符串，表示要从webapp/views/目录下找第一个以user-velocity.开始的页面
                // 运行本程序时，请在webapp/views/目录下创建一个名为user-velocity.vm的文件，写上写文本字符
                return "user-velocity";
            }
        }
    </code>
</pre>
<h3>3.2.3 返回一个jsp页面</h3>

<p><a href="http://localhost:8080/user/jsp" target="_blank">http://localhost:8080/user/jsp</a></p>
<pre>    <code>
        public class UserController {

            public String jsp() {
                // 在webapp/views/目录下创建user-jsp.jsp的文件即可 (UTF-8的)。
                return "user-jsp";
            }
        }
    </code>
</pre>
<h3>3.2.4 在页面渲染业务数据</h3>

<p><a href="http://localhost:8080/user/render" target="_blank">http://localhost:8080/user/render</a></p>
<pre>    <code>
        public class UserController {

            public String render(Invocation inv) {
                // 在vm/jsp中可以使用$now渲染这个值
                inv.addModel("now", new java.util.Date());
                // 在vm/jsp中可以使用$user.id, $user.name渲染user的值
                User user = new User();
                user.setId(1);
                user.setName("taomk");
                inv.addModel("user", user);
                return "user-render";
            }
        }
    </code>
</pre>
<h3>3.2.5 重定向(Redirect)</h3>

<p><a href="http://localhost:8080/user/redirect" target="_blank">http://localhost:8080/user/redirect</a></p>
<pre>    <code>
        public class UserController {

            public String redirect() {
                // 以r:开始表示重定向
                return "r:/user/test";
            }
        }
    </code>
</pre>
<h3>3.2.6 转发(Forward)</h3>

<p><a href="http://localhost:8080/user/forward" target="_blank">http://localhost:8080/user/forward</a></p>

<p><a href="http://localhost:8080/user/forward2" target="_blank">http://localhost:8080/user/forward2</a></p>

<p><a href="http://localhost:8080/user/forward3" target="_blank">http://localhost:8080/user/forward3</a></p>
<pre>    <code>
        public class UserController {

            public String forward() {
                // 大多数情况下，以/开始即是转发(除非存在webapp/user/test文件)
                return "/user/test";
            }

            public String forward2() {
                // a:开始表示转发到同一个控制器的acton方法forward()
                return "a:forward?note=可以带参数";
            }

            public String forward3() {
                // f:可以转发到同一个应用的任意action方法
                return "f:forward";
            }
        }
    </code>
</pre>
<h3>3.2.7 自定义方法映射</h3>

<p><a href="http://localhost:8080/user/list-by-group?groupId=123" target="_blank">http://localhost:8080/user/list-by-group?groupId=123</a>
</p>
<pre>    <code>
        public class UserController {

            @Get("list-by-group")
            public String listByGroup(@Param("groupId") String groupId) {
                return "@" + groupId;
            }
        }
    </code>
</pre>
<h3>3.2.8 使用正则表达式自定义方法映射</h3>

<p><a href="http://localhost:8080/user/list-by-group-abc" target="_blank">http://localhost:8080/user/list-by-group-abc</a></p>

<p><a href="http://localhost:8080/user/list-by-group-123" target="_blank">http://localhost:8080/user/list-by-group-123</a></p>

<pre>    <code>
        public class UserController {

            @Get("list-by-group-{groupId}")
            public String listByGroup2(@Param("groupId") String groupId) {
                return "@string-" + groupId;
            }

            @Get("list-by-group-{groupId:\\d+}")
            public String listByGroup3(@Param("groupId") int groupId) {
                return "@int-" + groupId;
            }

            @Get("list-by-group-n{groupId:\\d+}")
            public String listByGroup4(@Param("groupId") int groupId) {
                return "@int-" + groupId;
            }
        }
    </code>
</pre>
<p><strong>有问题？</strong>访问 <a href="http://localhost:8080/user/list-by-group-123" target="_blank">http://localhost:8080/user/list-by-group-123</a>
    打印出"string-123"而非“int-123”?</p>

<p>这是因为listByGroup2和listByGroup3的path定义的"非常一样"，Rose自身无法判断哪个优先级更高(对不起，我们还没找到给正则表达式排序的有效方法)。
    在此建议如下：</p>
<ul>
    <li>调整path定义：把第二个path改为list-by-group-n{groupId:\\d+}，然后通过<a href="http://localhost:8080/user/list-by-group-n123" target="_blank">http://localhost:8080/user/list-by-group-n123</a>
        访问它
    </li>
    <li>list-by-group-n{groupId} 的优先级高于 list-by-group-{gourpId}，Rose先“问”前者，只有前者不能处理时(abc不是数字，所以其不能处理)，才走后者</li>
    <li>想知道Rose的判断优先顺序？OK，请访问 <a href="http://localhost:8080/rose-info/tree" target="_blank">http://localhost:8080/rose-info/tree</a>
    </li>
    <li>当然，为安全考虑，rose-info/tree这个地址的可访问性，需要您明确把net.paoding.rose.web.controllers.roseInfo.TreeController的DEBUG
        log级别打开(通过log级别控制权限，算是我们特有的一种思路)
    </li>
</ul>
<h3>3.2.9 获取request请求参数</h3>

<p><a href="http://localhost:8080/user/param1?name=rose" target="_blank">http://localhost:8080/user/param1?name=rose</a>
</p>

<p><a href="http://localhost:8080/user/param2?name=rose" target="_blank">http://localhost:8080/user/param2?name=rose</a>
</p>

<p><a href="http://localhost:8080/user/param3/rose" target="_blank">http://localhost:8080/user/param3/rose</a></p>
<pre>    <code>
        public class UserController {

            public String param1(@Param("name") String name) {
                return "@" + name;
            }

            public String param2(Invocation inv) {
                return "@" + inv.getRequest().getParameter("name");
            }

            @Get("param3/{name}")
            public String param3(Invocation inv, @Param("name") String name) {
                // request.getParameter()也能获取@ReqMapping中定义的参数
                return "@method.name=" + name + "; request.param.name=" + inv.getRequest().getParameter("name");
            }
        }
    </code>
</pre>
<h3>3.2.10 数组参数</h3>

<p><a href="http://localhost:8080/user/array?id=1&id=2&id=3" target="_blank">http://localhost:8080/user/array?id=1&amp;id=2&amp;id=3</a>
</p>

<p><a href="http://localhost:8080/user/array?id=1,2,3,4" target="_blank">http://localhost:8080/user/array?id=1,2,3,4</a>
</p>
<pre>    <code>
        public class UserController {

            public String array(@Param("id") int[] idArray) {
                return "@" + Arrays.toString(idArray);
            }
        }
    </code>
</pre>
<h3>3.2.11 Map参数</h3>

<p><a href="http://localhost:8080/user/keyOfMap?map:1=paoding&map:2=rose" target="_blank">http://localhost:8080/user/keyOfMap?map:1=paoding&amp;map:2=rose</a>
</p>

<p><a href="http://localhost:8080/user/valueOfMap?map:1=paoding&map:2=rose" target="_blank">http://localhost:8080/user/valueOfMap?map:1=paoding&amp;map:2=rose </a>
</p>

<p><a href="http://localhost:8080/user/map?map:1=paoding&map:2=rose" target="_blank">http://localhost:8080/user/map?map:1=paoding&amp;map:2=rose</a>
</p>
<pre>    <code>
        public class UserController {

            public String keyOfMap(@Param("map") Map&lt;Integer, String&gt; map) {
                return "@" + Arrays.toString(map.keySet().toArray(new int[0]));
            }

            public String valueOfMap(@Param("map") Map&lt;Integer, String&gt; map) {
                return "@" + Arrays.toString(map.values().toArray(new String[0]));
            }

            public String map(@Param("map") Map&lt;Integer, String&gt; map) {
                StringBuilder sb = new StringBuilder();
                for (Map.Entry&lt;Integer, String&gt; entry : map.entrySet()) {
                    sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&lt;br&gt;");
                }
                return "@" + sb;
            }
        }
    </code>
</pre>
<h3>3.2.12 表单提交</h3>

<p>POST方式：<a href="http://localhost:8080/user/post?id=1&name=rose" target="_blank">http://localhost:8080/user?id=1&amp;name=rose</a>
</p>

<p>POST方式：<a href="http://localhost:8080/user/post2?id=1&name=rose&level.id=3" target="_blank">http://localhost:8080/user?id=1&amp;name=rose&amp;level.id=3</a>
</p>
<pre>    <code>
        public class UserController {

            @Post("post")
            public String post(User user) {
                return "@" + user.getId() + "=" + user.getName();
            }

            @Post("post2")
            public String post2(User user) {
                return "@" + user.getId() + "; level.id=" + user.getLevel().getId();
            }
        }
    </code>
</pre>
<h3>3.2.13 返回json</h3>

<p><a href="http://localhost:8080/user/json?id=1" target="_blank">http://localhost:8080/user/json?id=1</a></p>

<p><a href="http://localhost:8080/user/json2?id=2" target="_blank">http://localhost:8080/user/json2?id=2</a></p>
<pre>    <code>
        public class UserController {

            public Object json(@Param("id") String id) {
                JSONObject json = new JSONObject();
                json.put("id", id);
                json.put("name", "rose");
                json.put("text", "json");
                // rose将调用json.toString()渲染
                return json;
            }

            // 把JSONObject放到方法中，Rose将帮忙创建实例
            public Object json2(JSONObject json, @Param("id") String id) {
                json.put("id", id);
                json.put("name", "rose");
                json.put("text", "json2");
                // rose将调用json.toString()渲染
                return json;
            }
        }
    </code>
</pre>
<h3>3.2.14 返回xml</h3>

<p><a href="http://localhost:8080/user/xml" target="_blank">http://localhost:8080/user/xml</a></p>
<pre>    <code>
        public class UserController {

            public Object xml(Invocation inv) {
                User user = new User();
                user.setId(1);
                user.setName("rose");
                inv.addModel("user", user);
                // rose将调用user-xml.xml或.vm或.jsp渲染页面(按字母升序顺序优先: jsp, vm, xml)
                // 使用user-xml.xml的，默认contentType是text/xml;charset=UTF-8，语法同velocity
                // 使用user-xml.jsp或user-xml.vm的可在本方法中标注@HttpFeatures(contentType="xxx")改变
                // jsp的也可通过&lt;%@ page contentType="text/html;charset=UTF-8" %&gt;改变
                return "user-xml";
            }
        }
    </code>
</pre>
<p><strong>views/user-xml.xml</strong></p>
<pre>    <code>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;user&gt;
            &lt;id&gt;$user.id&lt;/id&gt;
            &lt;name&gt;$user.name&lt;/name&gt;
        &lt;/user&gt;
    </code>
</pre>
<h2>3.3 controller层：拦截器支持</h2>

<h3>3.3.1 拦截器作用</h3>
<ul>
    <li>面向切面编程（AOP）方法可以让一个项目更加关注核心逻辑，常见的一些最佳实践包括
        <ul>
            <li>权限</li>
            <li>缓存</li>
            <li>错误处理</li>
            <li>延时加载</li>
            <li>调试</li>
            <li>持久化</li>
            <li>资源池</li>
            <li>等等。。。</li>
        </ul>
    </li>
    <li>而此处的拦截器目标是在controller层提供各种在controller执行前、执行后的代码切入，以达到各种可AOP的目标。</li>
    <li>简单地说，拦截器能干的事情就是当你的项目写了一半时发现缺少啥全局要做的事情（比如需要验证权限），不用担心，搞一个拦截器就是了。</li>
</ul>
<p><strong>作用范围：</strong></p>

<p>rose拦截器实现AOP编程的一个手段，rose将这些拦截器分为2种：局部拦截器和全局拦截器。</p>

<p><strong>局部拦截器</strong></p>

<p>
    所谓局部拦截器是指，那些只能作用于某个模块的拦截器。当你把拦截器创建在controllers包或子包下，就意味它是一个局部拦截器；或者你把拦截器器创建在其他包下，但把它作为一个bean配置到controllers包或子包下的applicationContext.xml文件中，这时它也是一个局部拦截器。他被创建的地方或配置的地方称为“所在”的模块。特别的，对于通过配置形式的局部拦截器，其所在的模块可能有多个。
</p>

<p>
    局部拦截器只能应用于“所在”的模块或其子模块，其他模块不会看到这个拦截器。需要特别强调的，rose所称“模块”和package是相关的，但不完全一致。不同的package一定属于不同模块，相同的package，但属于不同的jar包、不同的classes地址也是不同的模块。
</p>

<p>
    局部拦截器默认可以应用于所在模块的子模块，但可以通过在该拦截器上标识@NotForSubModules禁止这种默认行为。
</p>

<p><strong>全局拦截器</strong></p>

<p>
    所谓全局拦截器是指，那些不是局部拦截器的其它拦截器，并且作为bean配置到root context中的拦截器。所谓root
    context默认是由WEB-INF/applicationContext*.xml、classes/applicationContext*.xml、xxx.jar/applicationContext*.xml组成的ApplicationContext。
</p>

<p>
    如果你的拦截器是公司级别的，希望在多个项目中共用的，就可以考虑提供一个单独的拦截器包，把拦截器创建在这个包中，并把它配置在src/applicationContext-interceptors.xml下。
</p>

<p>
    为了使jar文件根目录下的applicationContext*.xml能够被rose“认识到”，你需要在xxx.jar/META-INF下创建一个rose.properties文件，并写入一个属性rose=applicationContext
    。如果没有这个属性，即使你的xxx.jar包根目录含有applicationContext*.xml文件，也不会被rose识别。
</p>

<p><strong>作用范围与实际拦截的区别</strong></p>

<p>
    当一个拦截器作用于某个范围时，不代表就能够拦截到那个范围内的控制器。每个拦截器可以覆盖getRequiredAnnotationClass、isForAction等方法允许或排除某些控制器或方法，控制器及其方法也可以通过@Intercepted的allow和deny属性进行控制。
</p>

<h3>3.3.2 拦截器例子</h3>
<pre>    <code>
        // 直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)
        // 该标注可选，默认为false，设置oncePerRequest为true，表示如果当前的请求如果是被forward、include转来的，并且之前已经执行了该拦截器，则当前不再过该拦截器，
        // 在大部分情况下可以这样理解“一个请求只执行一次”
        @Interceptor(oncePerRequest = true)
        public class AccessTrackInterceptor extends ControllerInterceptorAdapter {

            // 调用控制器action方法之前,before方法被Rose调用
            @Override
            public Object before(Invocation inv) throws Exception {
                System.out.println("Execute AccessTrack.before()");
                return super.before(inv);
            }

            // 调用控制器action方法之后,after方法被Rose调用
            @Override
            protected Object after(Invocation inv, Object instruction) throws Exception {
                System.out.println("Execute AccessTrack.after()");
                return instruction;
            }

            @Override
            public void afterCompletion(final Invocation inv, Throwable ex) throws Exception {
                System.out.println("Execute AccessTrack.afterCompletion()");
            }

            @Override
            protected boolean isForAction(Method actionMethod, Class<!--?--> controllerClazz) {
                System.out.println("Execute AccessTrack.isForAction()");
                return super.isForAction(actionMethod, controllerClazz);
            }

            @Override
            public boolean isForDispatcher(Dispatcher dispatcher) {
                System.out.println("Execute AccessTrack.isForDispatcher()");
                return super.isForDispatcher(dispatcher);
            }

            @Override
            public void setPriority(int priority) {
                System.out.println("Execute AccessTrack.setPriority()");
                super.setPriority(priority);
            }

            @Override
            protected Object round(Invocation inv, InvocationChain chain) throws Exception {
                System.out.println("Execute AccessTrack.round()");
                return super.round(inv, chain);
            }

            // 覆盖这个方法，表示只有标注@AccessTrack的控制器或方法才会被此拦截器拦截
            @Override
            public Class&lt;? extends Annotation&gt; getRequiredAnnotationClass() {
                System.out.println("Execute AccessTrack.getRequiredAnnotationClass()");
                return AccessTrack.class;
            }
        }
    </code>
</pre>
<p>需要注意几点：</p>
<ul>
    <li>拦截器要放在controllers下</li>
    <li>继承net.paoding.rose.web.ControllerInterceptorAdapter</li>
    <li>按照实现的方法名，在controller执行前、中、后执行：
        <ul>
            <li>before：在controller执行前执行。</li>
            <li>after：在controller执行中（后）执行，如果一个返回抛出了异常，则不会进来。</li>
            <li>afterCompletion：在controller执行后执行，不论是否异常，都会进来。</li>
            <li>isForAction：定义满足某条件的才会被拦截。</li>
            <li>isForDispatcher：根据响应的情况判断是否拦截，比如说是正常请求、内部forward、还是include （但是没用过）。</li>
            <li>setPriority：设置一个数字表示拦截优先级，当有多个拦截器时，要精准控制，数字小的内层，大的在外层，在最外层的before方法最先执行，大家都执行完后它的after才最后执行。</li>
            <li>round：这才是真正的controller执行中执行，不过用得很少。</li>
            <li>getRequiredAnnotationClass：返回一个Annotation class name，表示这个拦截器只对此Annotation标过的controller才生效。常用。</li>
        </ul>
    </li>
</ul>
<p>标注类：AccessTrack</p>
<pre>    <code>
        // 这是一个annotation，所谓annotation就是一个“标签”，他的职责是“表明”
        // 至于表明之后该怎么样？则由其他代码来处理
        // 对于AccessTrack具体的处理代码是AccessTrackInterceptor.java
        @Inherited
        @Target( { ElementType.TYPE, ElementType.METHOD })
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface AccessTrack {
        }
    </code>
</pre>
<p>Controller类：AccessTrackController</p>
<pre>    <code>
        @AccessTrack
        public class AccessTrackController {

            public String interceptor() {
                return "@interceptor";
            }
        }
    </code>
</pre>
<p><a href="http://localhost:8080/accessTrack/interceptor" target="_blank">http://localhost:8080/accessTrack/interceptor</a></p>

<p>
    <strong>
        不使用标注，使用@Intercepted，直接引用Spring Bean id
    </strong>
</p>

<p>Interceptor类：AccessTrack2Interceptor</p>
<pre>    <code>
        // 直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)
        // 该标注可选，默认为false，设置oncePerRequest为true，表示如果当前的请求如果是被forward、include转来的，并且之前已经执行了该拦截器，则当前不再过该拦截器，
        // 在大部分情况下可以这样理解“一个请求只执行一次”
        @Interceptor(oncePerRequest = true)
        public class AccessTrack2Interceptor extends ControllerInterceptorAdapter {

            // 调用控制器action方法之前,before方法被Rose调用
            @Override
            public Object before(Invocation inv) throws Exception {
                System.out.println("Execute AccessTrack2.before()");
                return super.before(inv);
            }

            // 调用控制器action方法之后,after方法被Rose调用
            @Override
            protected Object after(Invocation inv, Object instruction) throws Exception {
                System.out.println("Execute AccessTrack2.after()");
                return instruction;
            }

            @Override
            public void afterCompletion(final Invocation inv, Throwable ex) throws Exception {
                System.out.println("Execute AccessTrack2.afterCompletion()");
            }

            @Override
            protected boolean isForAction(Method actionMethod, Class<!--?--> controllerClazz) {
                System.out.println("Execute AccessTrack2.isForAction()");
                return super.isForAction(actionMethod, controllerClazz);
            }

            @Override
            public boolean isForDispatcher(Dispatcher dispatcher) {
                System.out.println("Execute AccessTrack2.isForDispatcher()");
                return super.isForDispatcher(dispatcher);
            }

            @Override
            public void setPriority(int priority) {
                System.out.println("Execute AccessTrack2.setPriority()");
                super.setPriority(priority);
            }

            @Override
            protected Object round(Invocation inv, InvocationChain chain) throws Exception {
                System.out.println("Execute AccessTrack2.round()");
                return super.round(inv, chain);
            }
        }
    </code>
</pre>
<p>Controller类：AccessTrack2Controller</p>
<pre>    <code>
        @Intercepted(allow = {"accessTrack2"}, deny = {})
        public class AccessTrack2Controller {

            public String interceptor() {
                return "@interceptor2";
            }
        }
    </code>
</pre>
<p>Spring配置文件：applicationContext-interceptors.xml</p>
<pre>    <code>
        &lt;?xml version="1.0" encoding="UTF-8"?&gt;
        &lt;beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
            default-lazy-init="true"&gt;
                &lt;!--
                    注意：在使用@Intercepted(allow = {"accessTrack2"}, deny = {})进行拦截时：
                         1. id="accessTrackInterceptor" =&gt; allow、deny里面配置的值为accessTrack
                         2. id="accessTrack2" =&gt; allow、deny里面配置的值为accessTrack2
                --&gt;
                &lt;bean id="accessTrackInterceptor" class="com.qd.interceptors.AccessTrackInterceptor"/&gt;
                &lt;bean id="accessTrack2" class="com.qd.interceptors.AccessTrack2Interceptor"/&gt;
        &lt;/beans&gt;
    </code>
</pre>
<p><a href="http://localhost:8080/accessTrack2/interceptor" target="_blank">http://localhost:8080/accessTrack2/interceptor</a>
</p>

<h3>3.3.3 实际应用场景</h3>
<ul>
    <li>全站是否登录判断相关的逻辑，写在一个拦截器里，一次完成后，其他地方不再关心这个代码，在需要登录才能做的controller上注解一下，表示需要被执行拦截。</li>
    <li>日志收集的逻辑，在一个拦截器里进行当前的access log记录。</li>
    <li>权限体系的逻辑，写在一个拦截器里，在对应的操作上作注解，拦截器中进行细节的判断，新加的api也只是需要一次注解就得到了权限的判断。</li>
</ul>
<h2>3.4 controller层：ErrorHandler支持 【暂时还未调通，正在研究、、、】</h2>

<h3>3.4.1 ErrorHandler的作用</h3>
<ul>
    <li>一般来说传统的编程都会到处去try，特别是java里，try来try去的（如果你用erlang一定就知道，已经知道的可能性，怎么能叫异常？都try了还是让它崩了算了。。。）。</li>
    <li>如果打开你的项目，每个java文件中的代码都有一堆的try，那这时候就是ErrorHandle上阵的时候了。</li>
    <li>ErrorHandle致力于：统一捕捉和处理各种异常，可区分对待和返回；统一的出错体验。</li>
    <li>非常类似做web开发时的500统一出错页面这样的东东。</li>
</ul>
<h3>3.4.2 ErrorHandler的示例</h3>

<p>Controller类：HelloController</p>
<pre>    <code>
        @Path("hello")
        public class HelloController {

            public String nullPointerException() throws NullPointerException {
                return "@hello NullPointerException";
            }

            public String runtimeException() throws RuntimeException {
                return "@hello RuntimeException";
            }

            public String error() throws Exception {
                return "@hello Exception";
            }
        }
    </code>
</pre>
<p>Handler类：ErrorHandler</p>
<ul>
    <li>放在controllers目录下，和controller们在一起（幸福快乐地生活）。</li>
    <li>一般来讲，ErrorHandler都是用在web项目里，在controller层起作用。</li>
    <li>所有的方法都可以尽情地向上throws Exception了。</li>
    <li>不需要再try了。</li>
</ul>
<p>
    <strong>将异常让渡给上级模块的错误处理器处理</strong>
</p>

<p>
    controllers自己或其子package下都可以拥有独立的ControllerErrorHandler。
</p>

<p>
    如果在web调用过程中，控制器、拦截器等发生异常时，如果给定的module含有自己ControllerErrorHanlder时，则由他处理；如果自己没有则调用上级的ControllerErrorHandler处理。
</p>
<pre>    <code>
        // 直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)
        public class ErrorHandler implements ControllerErrorHandler {

            // 声明ParentErrorHandler,注意，这里不是ControllerErrorHandler
            // 万一上级没有ControllerErrorHandler, 这个字段也不会为空
            @Autowired
            private ParentErrorHandler parentErrorHandler;

            // 把方法第2个参数换上具体的异常类...这个onError就只处理所声明的这类异常
            public Object onError(Invocation inv, NullPointerException ex) {
                // 略去具体的处理代码......
                return "/views/500.jsp";
            }

            // 处理这个处理器只想处理的
            public Object onError(Invocation inv, RuntimeException ex) throws Throwable {
                System.out.println("---------RuntimeException----------");
                inv.getResponse().getWriter().write("&lt;pre&gt;RuntimeException&lt;br&gt;");
                ex.printStackTrace(inv.getResponse().getWriter());
                inv.getResponse().getWriter().write("&lt;/pre&gt;");
                return "";
            }

            // 通用的异常抛给上级ControllerErrorHanlder或上级的上级去处理
            @Override
            public Object onError(Invocation inv, Throwable ex) throws Throwable {
                return parentErrorHandler.onError(inv, ex);
            }
        }
    </code>
</pre>
<p><a href="http://localhost:8080/hello/nullPointerException" target="_blank">http://localhost:8080/hello/nullPointerException</a>
</p>

<p><a href="http://localhost:8080/hello/runtimeException" target="_blank">http://localhost:8080/hello/runtimeException</a></p>

<p><a href="http://localhost:8080/hello/error" target="_blank">http://localhost:8080/hello/error</a></p>

<p>这是这么简单，不用怀疑！</p>

<p>
    以上parentErrorHanlder的逻辑，Rose提供的ErrorHandlerAdapter类已经封装了
    <strong>建议您通过extends ErrorHandlerAdapter</strong> 实现错误处理器，而非直接实现ControllerErrorHandler
</p>
<ul>
    <li>
        处理这个处理器只想处理的
    </li>
    <li>
        通用的默认就会抛给上级模块的错误处理器处理
    </li>
</ul>
<pre>    <code>
        public class ErrorHandler extends ErrorHandlerAdapter {

            public Object onError(Invocation inv, RuntimeException ex) throws Throwable {
                System.out.println("---------RuntimeException----------");
                inv.getResponse().getWriter().write("&lt;pre&gt;RuntimeException&lt;br&gt;");
                ex.printStackTrace(inv.getResponse().getWriter());
                inv.getResponse().getWriter().write("&lt;/pre&gt;");
                return "";
            }
        }
    </code>
</pre>

<h2>3.5 controller层：自定义http参数支持</h2>

<h3>3.5.1 http参数支持的一些前言</h3>
<ul>
    <li>我们把一个controller的类里的一个方法叫做action，它实际对应用户看到的一个url。</li>
    <li>在action里可以接收各种各样的参数，也可以自己定义需要的参数。</li>
    <li>rose自己定义了一些常见的类型，基本上很少有机会会用到自己定义，但是在某些情况下，也是个不错的选择：
        <ul>
            <li>用来对指定的参数类型的值进行固定的修改和赋值。</li>
        </ul>
    </li>
</ul>
<h3>3.5.2 看一个例子</h3>

<p>TaoBeanResolver.java放在controllers目录下：</p>
<pre>    <code>
        // 直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)
        public class TaoBeanResolver implements ParamResolver {

            @Override
            public Object resolve(Invocation inv, ParamMetaData metaData) throws Exception {
                for (String paramName : metaData.getParamNames()) {
                    if (paramName != null) {
                        Tao tao = new Tao();
                        String first = inv.getParameter("tao1");
                        String second = inv.getParameter("tao2");
                        tao.setFirst(first);
                        tao.setSecond(second);
                        return tao;
                    }
                }
                return null;
            }

            @Override
            public boolean supports(ParamMetaData metaData) {
                return Tao.class == metaData.getParamType();
            }
        }
    </code>
</pre>
<ul>
    <li>
        <p>上述代码的意思：</p>
        <ul>
            <li>如果在action里一个参数的类型是Tao(com.qd.model.Tao)，就会走这个resolver，这里对两个参数进行了组装。</li>
            <li>用户如果访问的参数里传入了tao1和tao2的值，则会直接组装出来一个Tao对象。</li>
        </ul>
    </li>
    <li><p>配合上述resolver的action代码为：</p></li>
</ul>
<pre>    <code>
        public class UserController {

            public String param(Tao tao) throws Exception {
                return "@hello world:" + tao.getFirst() + ":" + tao.getSecond();
            }
        }
    </code>
</pre>
<p>
    <a href="http://localhost:8080/user/param?tao1=Tao&tao2=Mingkai" target="_blank">http://localhost:8080/user/param?tao1=Tao&amp;tao2=Mingkai</a>
</p>

<h3>3.5.3 rose内置的参数支持</h3>

<p>除了上述的自定义resolver外，rose还内置了丰富的resolver，都是大家的经验总结，使用起来会非常方便，它们是：</p>
<ul>
    <li>所有的基础java类型，都可以直接使用，rose进行自动转换，比如在action中的类型为long id，则id可以转为数字，不再需要从string转为long。</li>
    <li>array/map/bean同样可用，它们的接收参数规则为：
        <ul>
            <li>?id=1,2,3,4 或者 ?id=1&amp;id=2&amp;id=3 对应 @Param("id") int[] idArray</li>
            <li>?map:1=paoding&amp;map:2=rose 对应 @Param("map") Map map</li>
            <li>POST方式：<a href="http://localhost:8080/user/post2?id=1&name=rose&level.id=3" target="_blank">http://localhost:8080/user?id=1&amp;name=rose&amp;level.id=3</a>
            </li>
        </ul>
    </li>
</ul>
<h2>3.6 controller层：统一的参数验证办法</h2>

<h3>3.6.1 用来做什么</h3>
<ul>
    <li>我们把的参数验证办法叫ParamValidator</li>
    <li>一般来说，像比如说验证http传来的参数是不是为空呀啥的（发挥你的想象力）。</li>
    <li>好处在于不用再重复地写if else</li>
</ul>
<h3>3.6.2 怎么用</h3>

<p>来看一个例子，验证用户的参数不可为空(灰常灰常的实用)：</p>
<pre>    <code>
        // 直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)
        public class NotBlankParamValidator implements ParamValidator {

            @Override
            public boolean supports(ParamMetaData metaData) {
                return metaData.getAnnotation(NotBlank.class) != null;
            }

            @Override
            public Object validate(ParamMetaData metaData, Invocation inv, Object target, Errors errors) {
                String paramName = metaData.getParamName();
                String value = inv.getParameter(paramName);
                if (StringUtils.isBlank(value)) {
                    return "@参数不能为空";
                }
                return null;
            }
        }
    </code>
</pre>
<p>解读：</p>
<ul>
    <li>直接放在controllers或其子package下(也可以放到其他package下，但需要applicationContext配置)</li>
    <li>实现ParamValidator</li>
    <li>实现supports方法，这个方法用来做判断是否要验证当前得到的http参数，一般都用个注解来判断比较文艺</li>
    <li>实现validate方法，这里是主要逻辑：
        <ul>
            <li>metaData里放的是参数的原型</li>
            <li>inv是rose的基础调用</li>
            <li>target是这个参数的最后解析结果，参看上一节里提到的东西</li>
            <li>errors是这个参数解析时出来的错误</li>
        </ul>
    </li>
    <li>NotBlank是一个自己定义的annotation</li>
</ul>
<h3>3.6.3 使用时action长什么样</h3>
<pre>    <code>
        public class UserController {

            public String notBlank(@NotBlank @Param("messages") String messages) throws Exception {
                return "@hello world: notBlank " + messages;
            }
        }
    </code>
</pre>
<p>解读：</p>
<ul>
    <li>当遇到NotBlank注解的参数时，会自动执行参数判断</li>
    <li>如果messages为空，则会得到“参数不能为空”的返回</li>
</ul>
<p>
    <a href="http://localhost:8080/user/notBlank" target="_blank">http://localhost:8080/user/notBlank</a>
</p>

<p>
    <a href="http://localhost:8080/user/notBlank?messages=123" target="_blank">http://localhost:8080/user/notBlank?messages=123</a>
</p>

<h3>3.6.4 @IfParamExists的使用</h3>
<ul>
    <li>
        @IfParamExists标注在控制器的方法上，表示只有满足@IfParamExists定义的内容才有可能有该方法处理请求。
    </li>
    <li>
        不同的控制器方法，可以有相同的映射地址和HTTP方法，每个方法上配置不同的@IfParamExists
    </li>
    <li>
        @IfParamExists判断只在地址和HTTP方法已经满足的条件上才进行
    </li>
    <li>
        @IfParamExists("c") 表示只有存在c非空串参数才由该方法处理
    </li>
    <li>
        @IfParamExists("c=:[0-9]+") 表示只有存在c非空串参数，且参数值符合冒号后的正则表达式才由该方法处理
    </li>
    <li>
        @IfParamExists("c=3") 表示只有存在c参数且值为3时才由该方法处理
    </li>
    <li>
        如果有多个方法符合该规则，则c=3优先级大于c=:[0-9]+，c=:[0-9]+又大于c
    </li>
</ul>
<pre>    <code>
        public class UserController {

            @IfParamExists("msg")
            public String ifParamExists(@Param("msg") String msg) {
                return "@msg: " + msg;
            }

            @IfParamExists("msg=:[0-9]+")
            public String ifParamExists2(@Param("msg") String msg) {
                return "@msg=:[0-9]+: " + msg;
            }
        }
    </code>
</pre>
<p>
    <a href="http://localhost:8080/user/ifParamExists" target="_blank">http://localhost:8080/user/ifParamExists</a>
</p>

<p>
    <a href="http://localhost:8080/user/ifParamExists?msg=abc" target="_blank">http://localhost:8080/user/ifParamExists?msg=abc</a>
</p>

<p>
    <a href="http://localhost:8080/user/ifParamExists2?msg=123" target="_blank">http://localhost:8080/user/ifParamExists?msg=123</a>
</p>

<h2>3.7 controller层：一闪而过的信息，flash支持【实际意义不大、、、】</h2>

<h3>3.7.1 需求描述</h3>
<ul>
    <li>历史上，做web的需求时，经常遇到一个情况：在A页面修改/添加/删除了信息，提交，提示“修改/添加/删除成功！”。</li>
    <li>rose的flash（并非你所想象的adobe的flash）建设性地使这一需求在开发过程中简单快捷化。</li>
</ul>
<h3>3.7.2 使用过程</h3>
<ul>
    <li>使用过程会很愉快，在两个action之间，通过return "r:/xxx"来跳转（实际是301），只需要在第一个action里使用flash.put，在第二个action里使用flash.get即可。</li>
</ul>
<pre>    <code>
        public class UserController {

            public String flash1(Flash flash) {
                flash.add("msg", "修改成功！");
                return "r:/flash2";
            }

            public String flash2(Invocation inv, Flash flash) {
                inv.addModel("info", flash.get("msg"));
                return "flash";
            }
        }
    </code>
</pre>

<ul>
    <li>上述两个action中，当访问flash1时，一句flash信息被写入，快速跳转到flash2的地址。</li>
    <li>flash2地址中接收到这个flash信息后写到model中。</li>
    <li>还需要在flash2的模板里去显示这个变量。</li>
</ul>
<pre>    <code>
        &lt;%@ page contentType="text/html;charset=UTF-8" language="java" %&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;title&gt;&lt;/title&gt;
        &lt;/head&gt;
            &lt;body&gt;
                提示消息：${info}
            &lt;/body&gt;
        &lt;/html&gt;
    </code>
</pre>
<p>
    <a href="http://localhost:8080/user/flash1" target="_blank">http://localhost:8080/user/flash1</a>
</p>

<h3>3.7.3 注意事项</h3>
<ul>
    <li>
        <p>flash功能利用了浏览器的cookies功能，如果用户的环境不能使用cookies将不会有任何效果。</p>
    </li>
</ul>
<h2>3.8 controller层：门户必备portal支持</h2>

<h3>3.8.1 什么是portal</h3>
<ul>
    <li>字面意思，做门户用的。</li>
    <li>简单来说，把一个网页分成了N个区域，每个区域由不同的action去执行，多线程并行提高cpu使用率。</li>
</ul>
<h3>3.8.2 Portal原理</h3>
<ul>
    <li>
        rose portal是rose扩展开的一种重要技术，从某种角度其光耀甚至超过作为它基础的rose。
    </li>
    <li>
        rose portal是一个服务端portal技术，而非客户端技术。所谓服务端portal技术，是指所有工作发生在服务器上，客户端在整个过程中没有任何作用，使用rose portal不需要任何js技术的支持。
    </li>
    <li>
        使用rose portal，您可以将一个页面切成互不相关的多个子模块(我们称之为"window")，如果和配置组件结合，您可以根据不同用户的选择为之选择不同的模板以及窗口。
    </li>
    <li>
        虽然rose portal不依赖前端技术，但你也可以通过和前端技术的结合实现"rose pipe"技术，所谓rose pipe借助了rose
        portal，不仅仅能够加快了服务器的执行速度，更是降低了页面的渲染和传输等待时间。
    </li>
    <li>
        我们尽量把复杂和创意封装在实现代码中，暴露出来的api是极其简约的。开发一个portal程序，你只需要把Portal参数声明在方法上，并在方法中调用portal.addWindow就完成了整个代码结构。这就是和普通rose唯一不同的地方，剩下就都一样了。
    </li>
</ul>
<h3>3.8.3 技术决策</h3>

<p>本节简单介绍rose portal的技术方向决策，规范、统领技术的实现。</p>

<p><strong>转发</strong></p>

<p>servlet容器能够将一个请求从某一个地址转到另外的地址，由转发后的地址实际处理该请求。rose portal利用了这个特性。rose
    portal在处理window请求时，本质使用forward来实现。和普通的web请求不一样的地方是，所有window的请求都是由同一个起点地址（主控控制器）开始转发，而非由一个它转发给一个window，然后在由该window转发给下一个window。</p>

<p><strong>并发</strong></p>

<p>
    主控制器把请求转发给不同的window并不是串行的，即不是等某个window执行完毕后，再由主控制器转发给下一个window。rose
    portal在此作了特别处理，每当您调用portal.addWindow时，就意味着转发已经开始，但是这个转发是由另外的thread来执行的(称为异步)。portal.addWindow并不wait，而是立即返回，所以当您调用多次addWindow时，可能意味着由多少个thread在为你同时执行(如果线程池线程充足)。
</p>

<p><strong>线程池</strong></p>

<p>
    web容器本身提供了一个线程池，用于处理容器接收到的用户请求。rose portal框架下的主控制器是由这个线程池的线程执行的，但对于window控制器却非如此。rose
    portal另行依赖于独立容器线程池之外java.util.concurrent.ExecutorService服务，并把window的执行交由这个执行服务来执行。
</p>

<p>
    在具体实现上，使用的是java.util.concurrent.ThreadPoolExecutor，这个执行服务提供了一个线程池的策略。
</p>

<p><strong>输出</strong></p>

<p>
    作为一个服务端portal框架，无论服务端如何并行、甚至是“乱序”的并行，但是给用户的html或者其它的输出必须是一定的，和并行的调度无关的！
</p>

<p>
    rose
    portal要把每个window的输出独立出来，window执行服务可以和普通控制器那样合并数据到模板并调用out.println“输出”，但是不可以真的由window自己决定向客户端输出！真正的输出必须由主控线程来统一控制。
</p>

<p>
    rose portal目前采用的是window自行缓冲页面内容，并由主线程来负责实际输出的形式。
</p>

<h3>3.8.4 使用例子</h3>

<p>要使用portal，必须先在web.xml里声明所使用的线程池大小：</p>
<pre>    <code>
        &lt;!--
            - 如果没有配置以下参数，默认portalExecutorCorePoolSize取1，相当于除了http主线程外只有另外1个执行线程，
            - 这对程序的正确性没有任何影响，只是并发能力下降了，整个portal的执行时间也会变长。
        --&gt;
        &lt;context-param&gt;
            &lt;param-name&gt;portalExecutorCorePoolSize&lt;/param-name&gt;
            &lt;param-value&gt;1024&lt;/param-value&gt;
        &lt;/context-param&gt;
    </code>
</pre>
<p>然后看示例代码：</p>
<pre>    <code>
        public class UserController {

            // 设置@PortalSetting(timeout = 100)表示最多等待各个窗口100ms(包括window的页面渲染耗费时间)
            // 在主控控制方法上声明Portal参数即表示这个页面是portal，就这样!
            public String portal(Portal portal) {
                // 使用addWindow向这个portal页面加入各种子信息(我们成为窗口)
                portal.addWindow("p1", "/user/portal1");
                // 第一个参数是用于标识该窗口，使得portal页面中可以引用到这个窗口的html
                // 第二个参数表示这个窗口的地址(可以包含参数)，这个地址等价于forward的地址(也就是这里只要能forward的地址都可以，无论是否是rose框架的，甚至可以是一个jsp页面)
                portal.addWindow("p2", "/user/portal2");
                return "portal";
            }

            public String portal1(Invocation inv) {
                return "@this is portal1";
            }

            public String portal2(Invocation inv, Window window) {
                return "@this is portal2";
            }
        }
    </code>
</pre>
<p>然后在第一个action中的portal.jsp中写到：</p>
<pre>    <code>
        &lt;%@ page contentType="text/html;charset=UTF-8" language="java" %&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;title&gt;&lt;/title&gt;
        &lt;/head&gt;
            &lt;body&gt;
                ${p1}
                &lt;br&gt;
                ${p2}
            &lt;/body&gt;
        &lt;/html&gt;
    </code>
</pre>
<p>
    <a href="http://localhost:8080/user/portal" target="_blank">http://localhost:8080/user/portal</a>
</p>

<h3>3.8.5 高级话题</h3>
<ul>
    <li>
        可以在UserController.portal(Portal portal)方法上设置@PortalSetting(timeout = 100)表示最多等待各个窗口100ms(包括window的页面渲染耗费时间)
    </li>
    <li>
        可以在web.xml配置全局参数设置poral执行器的线程池参数：portalExecutorCorePoolSize、portalExecutorMaxPoolSize、portalExecutorKeepAliveSeconds，参数意义分别参考java.util.concurrent.ThreadPoolExecutor的corePoolSize、maximumPoolSize、keepAliveTime说明
    </li>
    <li>
        在portal.jsp页面中的$p1实际是一个net.paoding.rose.web.portal.Window对象，因此可通过$p1.success判断window的执行是否完成并且是200的，通过$p1.statusCode等了解具体的执行情况。详细请参考net.paoding.rose.web.portal.Window类属性列表。
    </li>
    <li>
        可在window的控制器UserController.portal2(Invocation inv, Window window)方法中声明Window
        window对象，通过window.setTilte(title)或window.set(name, value)相关属性，并在portal.jsp使用$p2.title。
    </li>
</ul>
<h3>3.8.6 这样子做的好处</h3>
<ul>
    <li>更加充分地使用多核cpu。</li>
    <li>更加方便多人协作时对项目进行模块划分，搞的时候，按照url一分，一个url一个模块，所有的页面都可以切成小的豆腐块，所以，你懂的。</li>
</ul>
<h2>3.9 controller层：门户必备pipe支持【暂时还未调通，正在研究、、、】</h2>

<h3>3.9.1 什么是pipe</h3>
<ul>
    <li>pipe起源于facebook的工程师对他们网页提速的方案：将网页分解为Pagelets的小块（在rose叫做window的小块），然后通过后端多重管道运行，以达到性能的最佳。</li>
    <li>pipe巧妙使用了http 1.1连接有timeout的机制，充分使用一次http连接来传递数据。</li>
    <li>pipe可使用户在大多数浏览器中感受到延迟减少了一半。</li>
</ul>
<h3>3.9.2 与facebook的bigpipe相比rose pipe如何</h3>
<ul>
    <li>fb并未在开源项目中公布过使用方法</li>
    <li>bigpipe神似是php+js搞定的</li>
    <li>rose pipe可以自由选择线程池大小，完全出自上一节的portal的基础</li>
    <li>完全实现bigpipe功能，天然的对业务开发者透明</li>
</ul>
<h3>3.9.3 看实例</h3>
<pre>    <code>
        public class UserController {

            public String pipe(Pipe pipe) {
                pipe.addWindow("p1", "/user/pipe1");
                pipe.addWindow("p2", "/user/pipe2");
                return "pipe";
            }

            public String pipe1() {
                return "@this is pipe1";
            }

            public String pipe2() {
                return "@this is pipe2";
            }
        }
    </code>
</pre>
<ul>
    <li>长得是不是很像上一节里提供的action？</li>
    <li>不同在于jsp文件中：</li>
</ul>
<pre>    <code>
        &lt;%@ page contentType="text/html;charset=UTF-8" language="java" %&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;title&gt;&lt;/title&gt;
            &lt;script type='text/javascript' src='/js/rosepipe.js'&gt;&lt;/script&gt;
        &lt;/head&gt;
            &lt;body&gt;
                pipe：
                &lt;br&gt;
                &lt;div id="p1"&gt;&lt;/div&gt;
                &lt;br&gt;
                &lt;div id="p2"&gt;&lt;/div&gt;
            &lt;/body&gt;
        &lt;/html&gt;
        &lt;rosepipe:write&gt;${p1}&lt;/rosepipe:write&gt;
        &lt;rosepipe:write&gt;${p2}&lt;/rosepipe:write&gt;
    </code>
</pre>
<ul>
    <li>当使用jsp文件时，需要在尾部使用rosepipe:write标签</li>
    <li>如果是使用vm文件，可以不写这个标签</li>
</ul>
<p>
    <a href="http://localhost:8080/user/pipe" target="_blank">http://localhost:8080/user/pipe</a>
</p>

<h3>3.9.4 总结</h3>
<ul>
    <li>上述代码中p1 p2两个window会同时在多个线程中执行，如果是portal，那会多个线程执行完成一起返回，而pipe则会用js反写的方式，一个线程一个线程地返回给用户。</li>
    <li>pipe是个好物件</li>
    <li>使用时jsp一定不要忘记尾部的标签</li>
    <li>使用时web.xml一定不要忘记声明使用的线程池大小</li>
</ul>
<h2>3.10 controller层：上传文件</h2>

<h3>3.10.1 其实很简单</h3>
<ul>
    <li>添加依赖包:commons-io.jar</li>
    <li>html中使用 enctype="multipart/form-data",method="POST"</li>
    <li>直接看后端代码吧。</li>
</ul>
<pre>    <code>
        public class UserController {

            // 接收name为"file"的input上传上来的文件
            // MultipartFile是一个Spring类
            public String upload(@Param("file") MultipartFile file) {
                // MultiPartFile提供了getName(), getInputStream(), transferTo()等方法可以方便使用
                return "@ok-"+ file.getName();
            }
        }
    </code>
</pre>
<p>
    POST方式：<a href="http://localhost:8080/user/upload" target="_blank">http://localhost:8080/user/upload</a>
</p>

<h3>3.10.2 其他</h3>
<pre>    <code>
        public class UserController {

            // 接收name为"file"的input上传上来的文件
            // MultipartFile是一个Spring类
            public String upload(@Param("file") MultipartFile file) {
                // MultiPartFile提供了getName(), getInputStream(), transferTo()等方法可以方便使用
                return "@ok-"+ file.getName();
            }

            // 接收name以"file"开始的input上传上来的所有文件
            // 接收名字为file1、file2等等的
            // files可以是一个数组或者List
            public String multiUpload(@Param("file") MultipartFile[] files) {
                return "@ok-" + Arrays.toString(files);
            }

            // 接收所有文件
            // 不声明@Param
            // files可以是一个数组或者List
            public String uploadAll(MultipartFile[] files) {
                return "@ok-" + Arrays.toString(files);
            }

            // 不声明@Param
            // 如果不声明为数组或List，只是一个MultipartFile,则取"第一个"上传的文件(特别适合于只上传一个文件的情况)
            public String uploadFirst(MultipartFile file) {
                return "@ok-" + file.getName();
            }

            // 接收名字为audio、video的input上传上来的文件
            public String upload(@Param("audio") MultipartFile audio, @Param("video") MultipartFile video) {
                return "@ok-";
            }

            // 接收名字为audio、video开始的input上传上来的所有文件
            public String upload(@Param("audio") MultipartFile[] audio, @Param("video") MultipartFile[] video) {
                return "@ok-";
            }

            // 接收名字为audio的input上传上来的文件，以及以video开始的input上传上来的所有文件
            public String upload(@Param("audio") MultipartFile audio,@Param("video") MultipartFile[] video) {
                return "@ok-";
            }
        }
    </code>
</pre>
<p>
    POST方式：<a href="http://localhost:8080/user/multiUpload" target="_blank">http://localhost:8080/user/multiUpload</a>
</p>

<h2>3.11 controller层：控制器类详解</h2>

<h3>3.11.1 定义</h3>

<p>在Rose框架中，控制器Controller是指扮演接收处理web请求职责的对象类。控制器接收web请求传入的参数、调度服务处理业务、将处理结果设置到model中，返回一个指示(页面)。</p>

<p>在Rose框架中，控制器是application-scope的，这和有些框架把控制器做成request-scope的对象是不一样的。</p>

<h3>3.11.2 创建控制器类</h3>
<ul>
    <li>控制器类package名规定：控制器只能放置在controllers或其子package下，controllers上可以有任意的父package，比如com.qd.xxx.yyy.controllers。</li>
    <li>控制器类类名规定：控制器命名应该以Controller结束，Controller前可以由任意字符组成(数字、下划线、字母)，比如UserController, OrderController。</li>
    <li>命名惯例：按照REST等建议，控制器命名应采用名词，如TopicController；方法命名应采用动词，如show, list, update, add, edit等。</li>
    <li>类修饰符：应写成public class，即：只能是public的，不能是abstract或interface。
    <pre>        <code>
            public class UserController {

            }
        </code>
    </pre>
    </li>
    <li>构造器：控制器必须默认或明确提供无参数的构造器。</li>
</ul>
<h3>3.11.3 控制器映射规则</h3>
<ul>
    <li>默认映射规则：去掉后缀，并把头字母小写化。
    <pre>        <code>
            /user --&gt; UserController
            /userXxx --&gt; UserXxxController
        </code>
    </pre>
    </li>
    <li>自定义映射规则：@Path("user-xxx") 可修改UserXxxController的映射规则变为：/user-xxx --&gt; UserXxxController；使用自定义映射后，原默认的映射规则不再存在。
    </li>
    <li>可使用正则表达式定义映射规则： @Path("user-{id:[0-9]+"})。</li>
    <li>可写@Path({"xxx", "yyy"})实现多个地址都映射到该控制器。</li>
</ul>
<h3>3.11.4 控制器域成员(Field)</h3>
<ul>
    <li>控制器是以单例的形式存在的，对于request-scope的信息，不能使用field记录。</li>
    <li>一般情况下除非是控制器对下层有 服务 或 资源 有依赖或有全局性的字段的必要，否则不书写field字段</li>
    <li>如果对下层有服务或资源的依赖，可直接在字段上并标注Spring提供的@Autowired注解
        <ul>
            <li>例如：@Autowired private UserService userService;</li>
            <li>也可以通过在setUserService(UserService userServer)方法上注@Autowired实现;</li>
        </ul>
    </li>
    <li>如果域成员是一个拦截器或拦截器数组、集合，这个域的拦截器将拦截到此控制器的web请求</li>
    <li>可以在控制器类中声明@Autowired private InvocationLocal inv ;这样可省去在method中声明Invocation inv；通过inv.addModel()可以向页面传递Value
        Object
    </li>
</ul>
<h3>3.11.5 控制器构造器(Constructor)</h3>
<ul>
    <li>必须有一个无参构造器(默认或明确书写)</li>
</ul>
<h3>3.11.6 控制器方法成员(Method)</h3>
<ul>
    <li>控制器可以包含任何形式、名称的Method，和就像普通类一样</li>
    <li>web请求可映射到控制器由其方法来处理，这类方法称为action方法：并不是控制器的所有方法都可以处理web请求</li>
    <li>只有在本控制器类声明的方法才有可能成为action方法，除非父类标注了@AsSuperController</li>
    <li>静态方法、非public的方法均不是action方法</li>
    <li>toString, hashCode, equals，wait，notify, notifyAll, finalize方法不是action方法，除非他们标注了@Get</li>
    <li>public Yyy getXxx(), public boolean/Boolean isXxx()不是action方法，除非他们标注了@Get</li>
    <li>public void setXxx(Yyy y)方法不是action方法，除非他们标注了@Post</li>
    <li>标注@Ignored的方法，不是action方法</li>
    <li>除以上范围之外的本类声明方法即是所属控制器的action方法</li>
</ul>
<h3>3.11.7 action方法规范</h3>
<ul>
    <li>就像普通方法一样：public 返回类型 方法名(参数列表) 异常声明 {方法体语句}
    <pre>        <code>
            @Get("{id}")
            public Object show(@Param("id") int id) throws Exception {
                return "user-show";
            }
        </code>
    </pre>
    </li>
    <li>public 是必须的，否则不是action方法</li>
    <li>返回类型通常应该是String类型(因为大多数web请求都是为了输出文本)
        <ul>
            <li>返回的值表示一个页面名称</li>
            <li>如果返回的值以r:开头表示redirect</li>
            <li>...</li>
        </ul>
    </li>
    <li>返回类型也可以是
        <ul>
            <li>基本类型或其封装类型，比如int， Integer，页面将直接显示其文本</li>
            <li>byte数组, InputStream类型，用于输出流；</li>
            <li>StringBuilder/Buffer, 普通对象的，用于输出他们的toString()结果</li>
        </ul>
    </li>
    <li>方法名按照通常Java规范遵守即可，一般应该是一个“动词”，表示对这个控制器名词类的一种操作</li>
    <li>参数可包括以下类型：Rose内置参数、简单类型、数组类型、集合类型、接口类型、对象类型</li>
    <li>可以声明抛出Exception等异常</li>
    <li>方法体语句按照普通的Java规范遵守即可，可以调用Invocation的addModel(name, value)方法设置要渲染的数据</li>
    <li>对只和单个请求相关数据，不要赋值给控制器Field字段(InvocationLocal inv除外)</li>
</ul>
<h3>3.11.8 action方法命名建议(建议而已)</h3>
<ul>
    <li>public String add() { } 创建增加信息</li>
    <li>public String show() { } 显示指定ID的对象信息</li>
    <li>public String list() { } 条件查询，分页显示</li>
    <li>public String edit() { } 编辑修改信息</li>
    <li>public String delete() { } 删除信息、注销信息</li>
</ul>
<h3>3.11.9 action方法映射规则</h3>
<ul>
    <li>默认映射规则： /list --&gt; public [返回类型] list(参数列表) 异常声明 方法体</li>
    <li>自定义映射规则：@Get("query")或@Post("query") 可修改方法映射规则变为：/query --&gt; 到所在的方法，原默认的映射规则不再存在；此时方法名不再参与映射规则判断中</li>
    <li>可使用正则表达式定义映射规则： @Get("user/{id:[0-9]+"})等</li>
    <li>可写@Get({"xxx", "yyy"})实现多个地址都映射到该方法</li>
    <li>可有2个方法名一样的action方法，但是需要通过@Get、@Post等注解的值区别开来</li>
</ul>
<h3>3.11.10 action方法参数规范</h3>
<ul>
    <li>action方法的参数的值将由Rose调用时提供</li>
    <li>action参数可包括以下几种类型：Rose内置的参数、基本类型参数、Bean参数等等</li>
    <li>
        基本类型参数应标注@Param("id")，其值将通过解析查询串或URI串的转化而来
        <pre>            <code>
                @Get("{id}")
                public String abc(@Param("id") int id, @Param("name") String name) {
                    System.out.println("id==&gt;" + id);
                }
            </code>
        </pre>
    </li>
    <li>
        Bean参数通常是用来存放提交的表单的数据的，一般不用写@Param
        <pre>            <code>
                @Post("post2")
                public String post2(User user) {
                    return "@" + user.getId() + "; level.id=" + user.getLevel().getId();
                }
            </code>
        </pre>
    </li>
    <li>Bean参数默认就已经放到model中了，其key是该bean的类名的头字母小写串(User --&gt; user)：标注@Param可以用来改变key</li>
</ul>
<h3>3.11.11 action方法参数说明</h3>
<ul>
    <li>
        内置参数
        <ul>
            <li>Invocation inv ：Rose封装与本次调用环境相关的对象，以下的内置参数都可以通过inv的方法获取到</li>
            <li>Model model ：同 inv.getModel()； 将对象add到model中，可在页面中使用类似"{user.id}"的表达式</li>
            <li>ServletContext ：同 inv.getServletContext()；当前ServletContext对象</li>
            <li>
                ServletRequest ：当前请求对象
            </li>
            <li>
                HttpServletRequest ：当前请求对象
            </li>
            <li>
                ServletResponse ：当前相应对象
            </li>
            <li>
                HttpServletResponse ：当前响应对象
            </li>
            <li>
                HttpSession ：当前会话，如果当前没有会话传入null，除非标准@Param(value = "session", required=true)
            </li>
            <li>
                MultipartFile ：上传的文件
            </li>
            <li>
                MultipartHttpServletRequest ：上传请求对象
            </li>
            <li>
                BindingResult ：绑定对象
            </li>
            <li>
                Errors ：绑定对象
            </li>
            <li>
                Flash ：Flash对象，用于redirect前后传递信息
            </li>
            <li>
                Portal ：Portal对象
            </li>
            <li>
                Pipe ：Pipe对象
            </li>
        </ul>
    </li>
    <li>
        简单类型
        <ul>
            <li>int/long/float/double/byte/short/boolean/char ：如果解析失败，值为0, false，'\0'</li>
            <li>Integer/Long/Float/Double/Byte/Short/Boolean/Character ：如果解析失败,值为null</li>
        </ul>
    </li>
    <li>
        数组类型
        <ul>
            <li>int/long/float/double/byte/short/boolean/char ：如果解析失败，值为null，哪怕只有数组的某一项解析失败</li>
            <li>Integer/Long/Float/Double/Byte/Short/Boolean/Character ：如果解析失败，值为null</li>
        </ul>
    </li>
</ul>
<h2>3.12 controller层：Invocation详解</h2>

<h3>3.12.1 Invocation是什么</h3>

<p>
    通常意义上的“Invocation”是指一次调用，特别地是指对方法的一次调用，是对“一次调用”的抽象与封装，比如包含了调用的输入参数、被调用的对象与方法等等。
</p>

<p>
    在Rose框架下，最终的处理者应该是某个控制器方法，与之相关的还有参数解析器、验证器以及很有知名度较高的拦截器。为了封装与一次请求处理相关的数据，我们设计了一个Invocation类来表示（习惯上用 inv 这 3
    个字母称呼它即可)。
</p>

<p>
    你可以从 inv 获取和本次调用有关的 HttpServletRequest、HttpServletResponse
    对象、本次调用的最终控制器类名以及控制器对象、方法对象以及参数等。同时inv还提供了相关的方法，setAttribute和 getAttribute 用于存储只与本次调用有关的属性(如果出现forward，则forward后的这些
    attribute 将不是直接可见的)、通过使用addModel(name, value)将数据传递给视图对象(比如jsp、velocity等)
</p>

<p>
    如果一个请求从一个控制器方法中 forward 到另外一个控制器方法，或 include 了另外的控制器方法，前后的inv对象将是不同的对象。当然 inv 也提供了 getPreInvocation
    是的能够知道当前当前请求是由哪个调用 forward 或 include 来的。特别的，倘诺一个请求 forward 或 include 了另外一个非 rose 能匹配的地址或页面的，这个页面有 forward 或 include
    了一个 rose 匹配的控制器方法，getPreInvocation 也将返回非空，且指得就是最近的那个rose控制器调用，而非中间那个页面。这个功能通常是没有什么特别的价值的，但在 portal 开发中却可以大显身手。
</p>

<h3>3.12.2 Invocation在哪里使用</h3>

<p>
    只有当rose匹配成功并且认定有对应的控制器方法能够处理该请求后，inv才会被创建。一旦创建之后，就可以在参数解析、验证中、拦截器、错误处理器中使用。这些组件都有一些标准的接口，你需要实现相应的方法来实现您的需要，这些方法都提供的inv参数，您可以直接使用。
</p>

<p>
    除了在上面所列的这些组件，还可以在“台风的中心”控制器方法上使用。您可以在控制器方法声明Invocation inv参数(按惯例应该是第一个参数)。这样，在控制器中你就可以通过inv.addModel(name,
    value)方法向视图组件传递要被渲染的业务数据。
</p>

<h3>3.12.3 inv.addModel 与 request.setAttribute</h3>

<p>
    在标准的servlet开发规范中，您会调用reqeust.setAttribute向jsp传递要被渲染的业务数据，这种做法没有任何问题。即使在rose框架中，如果您使用jsp作为渲染技术，你一样还可以这样使用。
</p>

<p>
    但是讲究一点的话，我们建议您使用addModel来做这件事情，这样Model-View-Controller就完整了。同时，假如您要使用 velocity
    的话，addModel就把数据自动送到vm页面，以后还有什么视图技术吗？如果有的话，通过 addModel 可以很好的实现。
</p>

<p>
    如果不讲究的话，即使您通过request.setAttribute来设置属性，rose一样有办法做到能够把数据提交给 velocity 渲染。但目前还处于“有办法”而已，因为这个“办法”没有被采用。
</p>

<p>
    总之，我建议使用概念更加清晰的addModel、而非request.setAttribute来履行MVC的功能。
</p>

<p>
    另外，需要您了解的是，addModel的具体实现并不是通过调用request.setAttribute来实现的。addModel加入的数据，通过reqeust.getAttribute是得不到的，除在jsp页面中。
</p>

<h1>第四章 DAO层：DAO的基本配置与使用</h1>

<h2>4.1 jade</h2>

<h3>4.1.1 什么是jade</h3>
<ul>
    <li>jade大概是java access data layer的意思吧。</li>
    <li>用jade的好处在于，尽可能减少重复的从db把数据对bean进行装配的过程，统一入口，隔离业务逻辑，方便review。</li>
    <li>jade是在spring完成的数据层的良好实践总结，无缝接入rose中，可以算得上是rose亲密无间的好模块。</li>
</ul>
<h3>4.1.2 引入基础配置</h3>
<ul>
    <li>要开始使用jade，一定要先引用jade的基础包，pom.xml：</li>
</ul>
<pre>    <code>
        &lt;dependency&gt;
            &lt;groupId&gt;net.paoding&lt;/groupId&gt;
            &lt;artifactId&gt;paoding-rose-jade&lt;/artifactId&gt;
            &lt;version&gt;1.2&lt;/version&gt;
        &lt;/dependency&gt;
    </code>
</pre>
<ul>
    <li>除了需要jade的包外，还需要引入数据源连接池的jar，这里使用了dbcp，当然了mysql-connector也是必不可少的，还是在pom.xml中添加：</li>
</ul>
<pre>    <code>
        &lt;dependency&gt;
            &lt;groupId&gt;commons-dbcp&lt;/groupId&gt;
            &lt;artifactId&gt;commons-dbcp&lt;/artifactId&gt;
            &lt;version&gt;1.4&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
            &lt;version&gt;5.1.27&lt;/version&gt;
        &lt;/dependency&gt;
    </code>
</pre>
<ul>
    <li>在pom中引入了依赖之后，需要定义一个数据源，这里先不考虑多个数据源的情况。applicationContext.xml中增加数据源定义：</li>
</ul>
<pre>    <code>
        &lt;context:property-placeholder location="classpath:db.properties" /&gt;

        &lt;!-- com.qd包对应数据源配置 dbcp --&gt;
        &lt;bean id="jade.dataSource.com.qd" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;
            &lt;property name="driverClassName" value="${driver.class}" /&gt;
            &lt;property name="url" value="${jdbc.url}" /&gt;
            &lt;property name="username" value="${user.name}" /&gt;
            &lt;property name="password" value="${user.pwd}" /&gt;
            &lt;!-- 运行判断连接超时任务的时间间隔，单位为毫秒，默认为-1，即不执行任务。 --&gt;
            &lt;property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /&gt;
            &lt;!-- 连接的超时时间，默认为半小时。 --&gt;
            &lt;property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /&gt;
        &lt;/bean&gt;
    </code>
</pre>
<p>jade约定：</p>
<ul>
    <li>1. 数据源bean的id为jade.dataSource.classPackageName，必须以jade.dataSource为前缀，后面接包名或类全名。</li>
    <li>2. 数据源bean的有效范围为classPackageName及其子目录下所有的DAO。</li>
</ul>
<p>按照jade要求，应用程序只需要在spring配置文件中按规范配置数据源，即能够被jade使用。</p>
<p>假如你有一个dao接口为com.qd.dao.xyz.UserDAO，那么jade默认情况下，按照以下规则来为此DAO配置DataSource：</p>
<ul>
    <li>是否有id为jade.dataSource.com.qd.dao.xyz.UserDAO的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource.com.qd.dao.xyz的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource.com.qd.dao的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource.com.qd的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource.com的bean? 如有，则该DAO此使用DataSource；</li>
    <li>是否有id为jade.dataSource的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为dataSource的bean? 如有，则该DAO使用此DataSource；</li>
</ul>
<p>
    如果没有以上任何一个配置，jade在执行SQL时（WEB非应用启动时）将抛出异常： not found dataSource for catalog: ''; you should set a dataSource bean
    (with id=<strong>'jade.dataSource[.daopackage[.daosimpleclassname]]' or 'dataSource'</strong> ) in applicationContext for this catalog。
</p>

<p>
    也因此，不同的DAO实际可以有不同的DataSource配置。
</p>

<h2>4.2 jade DAO</h2>

<h3>4.2.1 简单的DAO接口</h3>

<p>一个Jade DAO首先需要符合以下基本要求：</p>
<ul>
    <li>1. 在dao package或dao子package下，如com.qd.myapp.dao或com.qd.myapp.dao.sub；</li>
    <li>2. 必须是public的java interface 类型；</li>
    <li>3. 必须名称以大写DAO字母结尾，如UserDAO；</li>
    <li>4. 必须标注@DAO 注解；</li>
    <li>5. 不是其它类的内部接口；</li>
</ul>
<p>示例一：</p>
<pre>    <code>
        package com.qd.myapp.dao;
        @DAO
        public interface UserDAO {
        }
    </code>
</pre>
<p>示例二：</p>
<pre>    <code>
        Package com.mycompany.myapp.dao.subpackage;
        @DAO
        public interface CustomerDAO {
        }
    </code>
</pre>
<h3>4.2.2 具有继承关系的DAO接口</h3>

<p>
    一个接口(ChildDAO)可以扩展继承另外一个接口(ParentDAO)，只要ChildDAO符合《简单的DAO接口》的规定，它就是一个合格的Jade DAO接口类。
</p>

<p>
    如果ChildDAO没有标注@DAO，但其上级接口(包含祖先)标注了@DAO，则也被认为是标注了@DAO。
</p>

<p>示例一：</p>
<pre>    <code>
        package com.qd.myapp.dao.subpackage;
        import com.qd.myapp.dao.UserDAO;

        @DAO
        public interface ManagerDAO extends UserDAO {
        }
    </code>
</pre>
<p>示例二：</p>
<pre>    <code>
        package com.qd.myapp.dao.subpackage2;
        import com.qd.myapp.dao.UserDAO;
        import com.qd.myapp.dao.subpackage.CustomerDAO;

        // ComplexDAO没有标注@DAO，但它是一个合法的Jade DAO 接口；
        public interface ComplexDAO extends UserDAO, CustomerDAO {
        }
    </code>
</pre>
<h3>4.2.3 DAO方法</h3>

<p>一个DAO接口可以不限制地定义自己的方法，但只有标注 @SQL 注解的方法才被Jade识别为可以查询数据库的、真正的DAO方法。</p>

<p>示例一：</p>
<pre>    <code>
        @SQL("SELECT id, name FROM user LIMIT 1")
        public User findOne();
    </code>
</pre>
<p>示例二：</p>
<pre>    <code>
        @SQL("UPDATE user SET name=:2 WHERE id=:1")
        public void updateName(long userId, String userName);
    </code>
</pre>
<h3>4.2.4 DAO方法规范</h3>
<ul>
    <li>
        1. 必须标注 @SQL 注解;
    </li>
    <li>
        2. 返回类型可以是void，也可以根据不同应用情况进行声明，详看《返回类型》；
    </li>
    <li>
        3. 方法名称不限，但应该使用符合人类思维的名字以体现我们是正常的人类；
    </li>
    <li>
        4. 方法参数列表类型、顺序不限，但是应该要和 @SQL 中的 sql 语句的站位符对应起来；
    </li>
    <li>
        5. 方法参数如果是数组、集合、Map、Java Bean的，有特别的意义，详看《方法参数》；
    </li>
    <li>
        6. 可选声明抛出spring框架中的DataAccessException；
    </li>
</ul>
<p>示例一：</p>
<pre>    <code>
        @SQL("SELECT id, name FROM user WHERE name LIKE :1")
        public List&lt;User&gt; findByNameLike(String likeString) throws DataAccessException;
    </code>
</pre>
<p>示例二：</p>
<pre>    <code>
        @SQL("DELETE user WHERE id in (:1)")
        public int deleteUser(long[] userId) throws DataAccessException;
    </code>
</pre>
<h3>4.2.5 两种SQL类型</h3>

<p>Jade将 SQL语句分为两种类型："查询语句"和"非查询语句"，更进一步的，Jade默认只把以 SELECT 、SHOW、DESC、DESCRIBE 开始的 SQL 语句才识别为查询语句。</p>

<p>对于查询语句，Jade 会期望数据库返回一个结果集 ；非查询语句的，Jade 只是期望数据库返回一个更新的条目数。</p>

<p>对于存储过程，如果没有特别指定 Jade 会把它当成非查询类型。</p>

<h3>4.2.6 @SQL的基本用法</h3>

<p>@SQL用于标注在 DAO 方法上，表示这是一个 DAO 方法，同时说明执行这次 DAO 方法调用所要执行的 SQL 语句。</p>

<p>可以在 @SQL 中写入完整的一个 SQL 语句：</p>
<pre>    <code>
        @SQL("UPDATE user SET name='Mr. Right' WHERE id=3")
    </code>
</pre>
<p>或者是一个含有变量的 SQL 语句，此时需要使用 ":1"、":2"类似的符号表示一个变量，其中1、2表示方法的第i个参数(i=1、2、3 ……)：</p>
<pre>    <code>
        @SQL ("UPDATE user SET name=:2 WHERE id=:1")
        public void updateName(long userId, String userName);
    </code>
</pre>
<p>也可以使用:id, :name的形式，但这需要在具体的方法参数前加 @SQLParam 注解进行说明：</p>
<pre>    <code>
        @SQL ("UPDATE user SET name=:name WHERE id=:1")
        public void updateName(long userId, @SQLParam("name") String userName);
    </code>
</pre>
<p>
    如果要执行一个存储过程，亦可以写到@SQL中，但需要该存储过程返回结果集的，则可通过 type 来指定SQL 类型： @SQL(value = "…" type = SQLType.READ")；
</p>

<h3>4.2.7 DAO 方法的返回类型</h3>

<p>书写 DAO 方法的返回类型时要讲究，要根据不同的 SQL 类型做不同的处理。</p>

<p><strong>非查询类型语句</strong></p>

<p>非查询类型的 DAO 方法，Jade 只期待数据库返回一个整型数，表示此次执行数据库变更了多少行的记录。</p>

<p>
    支持的类型：<br>
    整型类型：int、Integer、boolean、Boolean、long、Long、byte、Byte、short、Short<br>
    浮点类型：float、Float、double、Double<br>
    精类型的：java.math.BigInteger、java.math.BigDecimal<br>
    特有类型：net.paoding.rose.jade.core.Identity<br>
    其它类型：java.lang.Number、void
</p>

<p>对其它不支持的类型，1.0版本将以 null 作为返回，以后版本或许有可能直接抛出异常。</p>

<p>如果你的 SQL 语句是一个插入语句，并且会产生一个自增 ID的，可通过声明返回类型为Idenity 明确返回该自增 ID，而非"记录变更数目"。</p>

<p><strong>查询类型语句</strong></p>

<p>查询类型的 DAO 方法的返回类型虽然力求简单、明了，但总体比较丰富，也比较复杂：</p>
<table class="wikitable">
    <tbody>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;"><strong>方法返回类型</strong></td>
        <td style="border: 1px solid #ccc; padding: 5px;"><strong>适用的情况</strong></td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">List&lt;User&gt;<br>Set&lt;User&gt;<br>User<a href=""></a>
        </td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回任意行，每行单列或多列的语句<br>SELECT id, name, age FROM user<br>SELECT
            id, name, age FROM user WHERE name LIKE :1
        </td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">List&lt;Map&lt;String, ?&gt;&gt;    </td>
        <td style="border: 1px solid #ccc; padding: 5px;">
            返回任意行，每行单列或多列的语句；<br>每一行映射为一个Map，列名为key，列值为value(具体类型不同列不同)<br>SELECT id, name, age FROM user
        </td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">User<br>Person <br>Student</td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回最多1行的语句；若返回0行，DAO 方法返回null给调用者；<br>如果实际结果集含有&gt;1行，抛IncorrectResultSizeDataAccessException<br>SELECT
            id, name, age FROM user WHERE id=:1<br> SELECT id, name, age FROM user LIMIT 1
        </td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">long <br> int<br> boolean<br> (基本类型)</td>
        <td style="border: 1px solid #ccc; padding: 5px;">返回1行，每行1列的语句；<br>如果返回0行，抛EmptyResultDataAccessException<br>如果返回&gt;1行，抛IncorrectResultSizeDataAccessException<br>SELECT
            id FROM user WHERE id &gt; :1 LIMIT 1<br>SELECT age FROM user WHERE id= :1<br>SELECT locked FROM user WHERE
            id=:1<br></td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">Long<br> Integer<br> String<br> (封箱类型)<br> byte<a href=""></a>
        </td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回最多1行，每行1列的语句；若返回0行，DAO 方法返回null给调用者；<br>如果实际结果集含有&gt;1行，抛IncorrectResultSizeDataAccessException<br>SELECT
            id FROM user WHERE id &gt; :1 LIMIT 1<br>SELECT age FROM user WHERE id= :1<br>SELECT bin_column FROM user
            WHERE id=:1 <br>// bin_column列可以是byte<a href=""></a>、blob类型的等
        </td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">List&lt;Integer&gt;<br> Set&lt;Long&gt;<br>
            int<a href=""></a> / Integer<a href=""></a><br> long<a href=""></a> / Long<a href=""></a><br> Byte<a href=""></a><br> (不包含byte<a href=""></a>)
        </td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回任意行，每行1列的语句；<br>SELECT id FROM user;<br>SELECT age FROM
            user LIMIT 100;<br>SELECT byte_column FROM user LIMIT 50;<br></td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">Map&lt;Long, String&gt;    </td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回任意行，每行2列，前一列为key，后一列为value的结果<br>SELECT id, name FROM user;
        </td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">Map&lt;Long, User&gt;    </td>
        <td style="border: 1px solid #ccc; padding: 5px;"> 返回任意行，每行不限制列，第一列为key，整行为value的结果<br>SELECT id, name, age FROM
            user;
        </td>
    </tr>
    </tbody>
</table>
<h3>4.2.8 Bean映射规则(默认)</h3>

<p>DAO 方法可以声明返回一个 Bean 或者 Bean 的数组或集合，以下陈述映射规则。</p>

<p>示例一：</p>
<pre>    <code>
        @SQL("SELECT id, first_name, flag_2,three_words_column FROM user LIMIT :1, :2")
        public List&lt;User&gt; find(int offset, int maxSize);
    </code>
</pre>
<table class="wikitable">
    <tbody>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">列</td>
        <td style="border: 1px solid #ccc; padding: 5px;">Bean</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">id</td>
        <td style="border: 1px solid #ccc; padding: 5px;"> public void setId(Xxx id)</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">first_name</td>
        <td style="border: 1px solid #ccc; padding: 5px;"> public void setFirstName(Xxx firstName)</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">flag_2</td>
        <td style="border: 1px solid #ccc; padding: 5px;">public void setFlag2(Xxx flag2)</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ccc; padding: 5px;">three_words_column</td>
        <td style="border: 1px solid #ccc; padding: 5px;"> public void setThreeWordsColumn(Xxx threeWordColumn)</td>
    </tr>
    </tbody>
</table>
<p>总结如下：</p>
<ul>
    <li>
        Jade会找相应的set方法，将值设置给Bean对象；
    </li>
    <li>
        如果列名是一个普通字符串，即不包含下划线、数字的列，如id，将首字母大写拼在set后：setId；
    </li>
    <li>
        如果列名含有下划线的，将下划线后的字母变为大写，first_name --&gt; setFirstName
    </li>
    <li>
        列名含有数字，且数字前没有下划线：
        <ul>
            <li>
                1.0.2版本：name2将映射给bean的name2属性了，user1_name2以及user_1_name_2列都能映射到user1Name2属性，但是user1_name_2不能映射给任何属性,必须写user1_name_2
                as user1_name2。
            </li>
            <li>1.0.1版本：name2(列名含有数字，且数字前没有下划线)，这样的字符串无法映射给任何 bean 属性，对此默认情况下jade会抛出InvalidDataAccessApiUsageException
                异常；这要通过as来做适配：SELECT name2 as name_2 FROM user，这样才能映射给bean的name2属性。
            </li>
        </ul>
    </li>
</ul>
<p>
    提醒一下的是，Jade使用默认使用的映射不仅限于此，但我强烈建议各位开发者严格按照这个规则来处理映射，因为哪一天Jade将更加严格(如果做了严格限制，一旦映射失败会提示InvalidDataAccessApiUsageException异常)。
</p>

<h3>4.2.9 自定义 Bean 映射</h3>

<p>示例一：使用自定义的UserRowMapper</p>
<pre>    <code>
        @SQL("SELECT id, name FROM user WHERE name LIKE :1")
        @RowHandler(rowMapper = UserRowMapper.class)
        public List&lt;User&gt; findByNameLike(String likeString) throws DataAccessException;

        public class UserRowMapper implements RowMapper {
            public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
                // code here
            }
        }
    </code>
</pre>
<h3>4.2.10 方法参数</h3>

<p>方法参数可以是和数据库列类型相对应Java类型，比如String、int、Integer、long、Long、java.util.Date、java.sql.Date等，也支持以下类型：数组、List、Set、Map、Java
    Bean。</p>

<p><strong>批量操作：使用List作为方法的第一个参数</strong></p>
<pre>    <code>
        @SQL("UPDATE XXX_TABLE set name=:1.name WHERE id=:1.id")
        public void changeNames(List&lt;User&gt; userList);

        userDAO.changeNames(aUserListHere);
    </code>
</pre>
<p><strong>in语句的写法：使用数组或集合类型作为参数的</strong></p>

<p>集合类型指实现Collection接口的一些类型，比如List、Set等。集合类型、数组 参数用于处理 in 的情况，而且这个数组或集合类型的元素应该是一些数据库列相对应的Java类型：</p>

<p>示例一：</p>
<pre>    <code>
        @SQL("SELECT name WHERE id IN (:1)")
        public String[] findNames(long[] idArray);
    </code>
</pre>
<p>示例二：</p>
<pre>    <code>
        @SQL("SELECT name WHERE id IN (:1)")
        public String[] findNames(List&lt;Long&gt; idList);
    </code>
</pre>
<p>实际执行SQL调用时，将根据数组、集合类型的长度重写SQL语句，比如，当传入的是一个长度为3的数组时候，调用</p>
<pre>    <code>
        userDAO.findNames(new long[]{1,2,3});
    </code>
</pre>
<p>Jade将把SQL语句改写为由3个问号组成的语句：</p>
<pre>    <code>
        SELECT name WHERE id IN (?, ?, ?)
    </code>
</pre>
<p>注意，IN语句的查询不能这样处理：</p>
<pre>    <code>
        @SQL("SELECT name FROM XXX_TABLE WHERE id IN (:1)")
        public String[] findNames(String idList);

        userDAO.findNames("1,2,3");
    </code>
</pre>

<p><strong>Bean作为参数</strong></p>

<p>通过 ’.’ 号来使用Bean 的属性，将其作为 SQL 参数。</p>

<p>示例一: 使用 :1.id、:1.name、:1.age</p>
<pre>    <code>
        @SQL("INSERT user (id, name, age) VALUES (:1.id, :1.name, :1.age)")
        public void save(User user);
    </code>
</pre>

<p><strong>批量操作：Map作为参数</strong></p>

<p>通过方括号来使用 Map 的值，将其作为 SQL 参数。</p>

<p>示例一: 使用 :1[id]、:1[name]、:1[age]</p>
<pre>    <code>
        @SQL("INSERT user (id, name, age) VALUES (:1[id], :1[name], :1[age])")
        public void save(Map&lt;String, ?&gt; userProperties);
    </code>
</pre>

<p><strong>批量操作：Array作为参数</strong></p>

<p>类似Map的使用，通过方括号来使用 数组 的值，将其作为 SQL 参数。</p>

<p>示例一: 使用 :1[0]、:1[1]、:1[2]</p>
<pre>    <code>
        @SQL("INSERT user (id, name, age) VALUES (:1[0], :1[1], :1[2])")
        public void save(Object[] userProperties);
    </code>
</pre>

<h2>4.3 第一个读取数据库的实例</h2>
<ul>
    <li>先需要准备一下数据库SQL：</li>
</ul>
<pre>    <code>
        DROP TABLE IF EXISTS `test`;
        CREATE TABLE `test` (`id` int(11) NOT NULL AUTO_INCREMENT, `msg` varchar(255) NULL DEFAULT NULL, PRIMARY KEY(`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

        insert into test values(111,'adfafafasdf');
    </code>
</pre>

<ul>
    <li>然后准备简练的DAO接口：</li>
</ul>
<pre>    <code>
        @DAO
        public interface TestDAO {

            @SQL("SELECT id, msg FROM test LIMIT 1")
            public Test getTest();
        }
    </code>
</pre>
<ul>
    <li><p>Test是一个class，里面有标准的getter和setter。</p></li>
    <li><p>然后从一个Service类去调用DAO接口：</p></li>
</ul>
<pre>    <code>
        public interface TestService {
            public Test getTest();
        }

        @Service
        public class TestServiceImpl implements TestService {

            @Autowired
            private TestDAO testDAO;

            public Test getTest() {
                return testDAO.getTest();
            }
        }
    </code>
</pre>
<ul>
    <li><p>最后，Controller层：</p></li>
</ul>
<pre>    <code>
        public class UserController {

            @Autowired
            private TestService testService;

            public String jadeTest() {
                return "@" + testService.getTest().toString();
            }
        }
    </code>
</pre>
<ul>
    <li><p><a href="http://localhost:8080/user/jadeTest" target="_blank">http://localhost:8080/user/jadeTest</a></p>
    </li>
</ul>
<h2>4.4 SQLParam介绍：DAO方法传递参数</h2>
<ul>
    <li>SQLParam作为DAO支持中的参数传递使者，可以传递一个常见的变量，也可以是一个自定义的对象。</li>
    <li>比如：</li>
</ul>
<pre>    <code>
        @DAO
        public interface TestDAO {

            @ReturnGeneratedKeys
            @SQL("insert into test (msg) values (:t.msg)")
            public int insertTest(@SQLParam("t") Test test);
        }
    </code>
</pre>
<ul>
    <li><p>上列中Test对象通过t传递到sql执行中去，并且可以分别使用其中的属性。这感觉是不是很自然？</p></li>
    <li><p>当然，如果是一个int、long、String等自在不言中。</p></li>
    <li><p>当是list时，会有自动的batch操作，将sql拆为多条sql执行。</p></li>
    <li><p>@ReturnGeneratedKeys 返回当前插入的ID号</p></li>
    <li><p>特别是使用mysql开发的广大劳苦大众，常常会使用到auto_increament的字段。</p></li>
    <li><p>当一条insert语句在执行的时候，我们常常会去需要拿它的当前的自增id是多少。</p></li>
</ul>
<p>
    Controller层:
</p>
<pre>    <code>
        public class UserController {

            @Autowired
            private TestService testService;

            @IfParamExists("name")
            public String insertTest(@Param("name") String name) {
                Test test = new Test();
                test.setMsg(name);
                return "@insert id = " + testService.insertTest(test);
            }
        }
    </code>
</pre>
<p><a href="http://localhost:8080/user/insertTest?name=TaoMingkai" target="_blank">http://localhost:8080/user/insertTest?name=TaoMingkai</a>
</p>

<h2>4.5 表达式的支持</h2>
<ul>
    <li>多变的业务需求决定了我们的sql是复杂的，需要有条件地执行。</li>
    <li>如果每种条件都去写DAO中的SQL，那DAO的变得很大。</li>
    <li>常常会有动态产生sql的需求。</li>
    <li><p>jade支持一些常规的表达式。</p></li>
    <li>
        <p>语法一：常见的变量赋值</p>

        <ul>
            <li>冒号（:）表示这是一个变量，比如上面的例子里的 :t.id，它会被一个值替换。</li>
        </ul>
    </li>
    <li>
        <p>语法二：字符串连接</p>

        <ul>
            <li>连续的井号（##） 表示后面的变量作字符串连接</li>
            <li>如下例中的partition变量，还请不要误解，分表不是这样做的，下一章会介绍标准的分表设置。</li>
        </ul>
    </li>
</ul>
<pre>    <code>
        @SQL("SELECT user_id, device_token FROM test_##(:partition) LIMIT :limit")
        public List&lt;Test&gt; getTests(@SQLParam("partition") int partition, @SQLParam("limit") int limit);
    </code>
</pre>


<ul>
    <li>语法三：条件选择

        <ul>
            <li>井号if（#if{}）用于表示当条件满足时sql拼接。</li>
        </ul>
    </li>
</ul>
<pre>    <code>
        @SQL("SELECT user_id, device_token FROM test_##(:partition) #if(:user&gt;0){ where user_id=:user } LIMIT :limit")
        public List&lt;Test&gt; getTestsIf(@SQLParam("partition") int partition, @SQLParam("limit") int limit, @SQLParam("user") int user);
    </code>
</pre>

<ul>
    <li>其他语法：还有for循环，实际使用少。</li>
    <li>典型地，一般的select in查询，可以直接传入list，例如下例中的ids变量：</li>
</ul>
<pre>    <code>
        @SQL("SELECT user_id, device_token FROM test_##(:partition) where user_id in(:ids)")
        public List&lt;Test&gt; getTestsByIds(@SQLParam("partition") int partition, @SQLParam("ids") List&lt;Integer&gt; ids);
    </code>
</pre>

<h1>第五章 DAO层：分表设置</h1>

<h2>5.1 散表功能</h2>

<p>
    根据一些参数把程序中的SQL中的表名映射为数据库中实际的表名，比如程序中有一条SQL “select id, name from user”,
    但实际的数据库用来存储该user数据的表不止一个，而是十个，表名分别是user_0、user_1、...、user_9。
</p>

<p>
    因此，在执行该select的时侯，jade就必须根据某些条件，将送到MySQL服务器的语句解析为实际的SQL，例如select id, name from user_1 。
</p>

<p>
    支持这种特性的数据库设计，我们称之为具有散表功能。
</p>

<h2>5.2 分表的常规做法</h2>

<p>以下分表规则特别在mysql中使用机会比较多，各有优势，没有对错，只有最好与最不好用：</p>
<ul>
    <li>按照 id % 100 分为100份</li>
    <li>按照 id % 16 分为16份</li>
    <li>按照 id/10 % 10 分为10份</li>
    <li>按照 id%10 分为10份</li>
</ul>
<h3>5.2.1 使用分表第一步：添加新的依赖</h3>

<p>要使用分表，需要添加新的依赖，由bmw提供的bmwutils。</p>
<pre>    <code>
        &lt;dependency&gt;
            &lt;groupId&gt;com.54chen&lt;/groupId&gt;
            &lt;artifactId&gt;bmwutils&lt;/artifactId&gt;
            &lt;version&gt;0.0.2&lt;/version&gt;
        &lt;/dependency&gt;
    </code>
</pre>

<h3>5.2.2 使用分表第二步：设置applicationContext.xml分表规则</h3>

<p>在开写代码之前，需要告诉DAO是哪个表需要分表，按照什么规则分，分多少份。</p>
<pre>    <code>
        &lt;!-- 以下配置为分表设置 --&gt;
        &lt;bean id="jade.routerInterpreter" class="com.xiaomi.common.service.dal.routing.RewriteSQLInterpreter"&gt;
            &lt;property name="routingConfigurator" ref="jade.routingConfigurator" /&gt;
        &lt;/bean&gt;
        &lt;bean id="jade.routingConfigurator" class="com.xiaomi.common.service.dal.routing.RoutingConfigurator"&gt;
            &lt;property name="partitions"&gt;
                &lt;list&gt;
                    &lt;!-- hash:user:id:user_{0}:10 表示：使用hash这种办法，将user这个表，按照id的值，分成10份，每份的表名为user_x --&gt;
                    &lt;value&gt;hash:user:id:user_{0}:10&lt;/value&gt;
                &lt;/list&gt;
            &lt;/property&gt;
        &lt;/bean&gt;
    </code>
</pre>
<ul>
    <li>此处配置中，partitions参数为一个list，可以对多个table进行定义。

        <ul>
            <li>hash:user:id:user_{0}:10 表示：使用hash这种办法，将user这个表，按照id的值，分成10份，每份的表名为user_x</li>
        </ul>
    </li>
</ul>
<h3>5.2.3 使用分表第三步：bmwutils支持的分表办法</h3>
<ul>
    <li>(hash)上例中的hash: 最常用的 id % 100 就是这种办法。该办法会把传入的值先进行转为数字后与定义的份数进行取模（%）。</li>
    <li>(direct)最直接的一种：用的少一些，没有什么规则，直接根据第四个正则，与第三位传入的值进行替换。假设有个人名表，按照字母分表可以用。name_A,name_B,name_C...</li>
    <li>(round)轮循：根据设置，按照调用sql的情况，轮流使用各个表。</li>
    <li>(range)范围：一般用来做日期范围的分表，比如说微博类的，可变值为一个时间，当时间传入时，第三位支持log<em>{yyyy} log</em>{yyyy_MM}等时间格式的替换，可轻松做到按周、月、年分表。</li>
    <li>(xm-hash)小米hash：一种古怪的办法，按照传入值的十位进行取模的分表方案。</li>
    <li>(xm-str-hash)小米字符串hash：将字符串按照固定算法变成long之后，再按照小米hash逻辑处理。</li>
    <li>(hex-hash)16进制分表：固定256份以内，传入的值按照16进制转换后按hash求模。</li>
</ul>
<h3>5.2.4 第一个分表的实例</h3>

<p>SQL：分表语句</p>
<pre>    <code>
        DROP TABLE IF EXISTS `user_0`;
        CREATE TABLE `user_0` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_1`;
        CREATE TABLE `user_1` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_2`;
        CREATE TABLE `user_2` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_3`;
        CREATE TABLE `user_3` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_4`;
        CREATE TABLE `user_4` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_5`;
        CREATE TABLE `user_5` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_6`;
        CREATE TABLE `user_6` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_7`;
        CREATE TABLE `user_7` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_8`;
        CREATE TABLE `user_8` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        DROP TABLE IF EXISTS `user_9`;
        CREATE TABLE `user_9` (`id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    </code>
</pre>
<p>DAO层：UserDAO</p>
<pre>    <code>
        @DAO
        public interface UserDAO {

            @SQL("SELECT id, name FROM user")
            public List&lt;User&gt; getUsersByShardId(@ShardBy @SQLParam("id") int shardId);

            @ReturnGeneratedKeys
            @SQL("INSERT INTO user (name) values (:u.name)")
            public int insertUserByShardId(@ShardBy @SQLParam("id") int shardId, @SQLParam("u") User user);

            @SQL("SELECT id, name FROM user WHERE name=:name ")
            public User getUserByName(@ShardBy @SQLParam("id") int shardId, @SQLParam("name") String name);
        }
    </code>
</pre>
<p>Service层：UserServiceImpl</p>
<pre>    <code>
        @Service
        public class UserServiceImpl implements UserService {

            @Autowired
            private UserDAO userDAO;

            @Override
            public List&lt;User&gt; findAll() {

                List&lt;User&gt; userList = new ArrayList&lt;User&gt;();

                for (int i =0; i &lt; 10; i++) {

                    userList.addAll(userDAO.getUsersByShardId(i));
                }

                return userList;
            }

            @Override
            public int add(User user) {
                return userDAO.insertUserByShardId(user.getName().hashCode(), user);
            }

            @Override
            public User findByName(String name) {
                return userDAO.getUserByName(name.hashCode(), name);
            }
        }
    </code>
</pre>
<p>Controller层：UserController</p>
<pre>    <code>
        public class UserController {

            @Autowired
            private UserService userService;

            public String findAllShardUsers() {
                StringBuilder sb = new StringBuilder();
                List&lt;User&gt; userList = userService.findAll();

                for (User user : userList) {

                    sb.append(user.toString()).append("&lt;br&gt;");
                }
                return "@" + sb.toString();
            }

            @IfParamExists("name")
            public String addShardUserByName(@Param("name") String name) {
                User user = new User();
                user.setName(name);

                int id = userService.add(user);
                return "@insert user id = " + id;
            }

            @IfParamExists("name")
            public String findShardUserByName(@Param("name") String name) {

                User user = userService.findByName(name);
                return "@findShardUserByName = " + user.toString();
            }
        }
    </code>
</pre>

<p><a href="http://localhost:8080/user/addShardUserByName?name=taomingkai" target="_blank">http://localhost:8080/user/addShardUserByName?name=taomingkai</a>
</p>

<p><a href="http://localhost:8080/user/findShardUserByName?name=taomingkai" target="_blank">http://localhost:8080/user/findShardUserByName?name=taomingkai</a>
</p>

<p><a href="http://localhost:8080/user/findAllShardUsers" target="_blank">http://localhost:8080/user/findAllShardUsers</a></p>

<h1>第六章 DAO层：分库设置、读写分离</h1>
<h2>6.1 数据源配置</h2>
<p>
    applicationContext-datasource.xml：
</p>
<pre>    <code>
        &lt;context:property-placeholder location="classpath:db.properties" /&gt;

        &lt;!-- 默认数据源配置 dbcp --&gt;
        &lt;bean id="jade.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;
            &lt;property name="driverClassName" value="${driver.class}" /&gt;
            &lt;property name="url" value="${jdbc.url}" /&gt;
            &lt;property name="username" value="${user.name}" /&gt;
            &lt;property name="password" value="${user.pwd}" /&gt;
            &lt;!-- 运行判断连接超时任务的时间间隔，单位为毫秒，默认为-1，即不执行任务。 --&gt;
            &lt;property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /&gt;
            &lt;!-- 连接的超时时间，默认为半小时。 --&gt;
            &lt;property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /&gt;
        &lt;/bean&gt;

        &lt;!-- com.qd包对应数据源配置 dbcp --&gt;
        &lt;bean id="jade.dataSource.com.qd" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;
            &lt;property name="driverClassName" value="${driver.class}" /&gt;
            &lt;property name="url" value="${jdbc.url}" /&gt;
            &lt;property name="username" value="${user.name}" /&gt;
            &lt;property name="password" value="${user.pwd}" /&gt;
            &lt;!-- 运行判断连接超时任务的时间间隔，单位为毫秒，默认为-1，即不执行任务。 --&gt;
            &lt;property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /&gt;
            &lt;!-- 连接的超时时间，默认为半小时。 --&gt;
            &lt;property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /&gt;
        &lt;/bean&gt;

        &lt;!-- 只读数据源配置 dbcp --&gt;
        &lt;bean id="jade.dataSource.qd_community_ro" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;
            &lt;property name="driverClassName" value="${driver.class}" /&gt;
            &lt;property name="url" value="${jdbc.url}" /&gt;
            &lt;property name="username" value="${user.name}" /&gt;
            &lt;property name="password" value="${user.pwd}" /&gt;
            &lt;!-- 运行判断连接超时任务的时间间隔，单位为毫秒，默认为-1，即不执行任务。 --&gt;
            &lt;property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /&gt;
            &lt;!-- 连接的超时时间，默认为半小时。 --&gt;
            &lt;property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /&gt;
        &lt;/bean&gt;
    </code>
</pre>
<h2>6.2 DAO接口：数据源使用</h2>
<p>
    catalog属性：通过该属性可以指定不同数据源，如：@DAO(catalog = "myteam.myapp")、@DAO(catalog = "qd_community_ro")，则视myteam.myapp、qd_community_ro等同于package名，jade在为该DAO配置DataSource时，DataSource查找规则如下：
</p>
<ul>
    <li>是否有id为jade.dataSource.myteam.myapp的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource.myteam的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为jade.dataSource的bean? 如有，则该DAO使用此DataSource；</li>
    <li>是否有id为dataSource的bean? 如有，则该DAO使用此DataSource；</li>
</ul>
<p>
    <strong>注意：如果jade.dataSource.com.qd与jade.dataSource.qd_community_ro数据源同时配置时，@DAO(catalog = "qd_community_ro")则会使用jade.dataSource.com.qd数据源；</strong>
</p>
<p>
    如果没有以上任何一个配置，jade在执行SQL时（WEB非应用启动时）将抛出异常： not found dataSource for catalog: ''; you should set a dataSource bean
    (with id=<strong>'jade.dataSource[.daopackage[.daosimpleclassname]]' or 'dataSource'</strong>) in applicationContext for this catalog。
</p>
<pre>    <code>
        package com.qd.lib.dao;

        import net.paoding.rose.jade.annotation.DAO;
        import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
        import net.paoding.rose.jade.annotation.SQL;

        import java.util.List;

        import com.qd.lib.model.Book;

        // 必须是接口，并且以大写DAO结尾
        // 必须标注@DAO，DAO中有一个catalog属性，对于大部分人来说，这个都是没用的
        @DAO(catalog = "qd_community_ro")
        public interface BookDAO {

            @SQL("select id, name, price, author from book where id = :1")
            public Book get(long bookId);

            @SQL("select id, name, price, author from book order by id desc limit :1")
            public List&lt;Book&gt; query(int limit);

            @SQL("select id, name, price, author from book where id &lt; :1 order by id desc limit :2")
            public List&lt;Book&gt; query(long bookId, int limit);

            @SQL("update book set name=:1.name, price=:1.price, author=:1.author where id=:1.id")
            public void update(Book book);

            @ReturnGeneratedKeys
            @SQL("insert into book (name, price, author) values (:1.name, :1.price, :1.author)")
            public long insert(Book book);
        }
    </code>
</pre>
<p>
    <a href="http://localhost:8080/lib/book" target="_blank">http://localhost:8080/lib/book</a>
</p>

<h1>第七章 安全</h1>
<ul>
    <li>在controller中，如果你不想将一个方法公开对外，那就不要将其标识为public，因为public的方法会被rose认为是一个要公开的action。</li>
</ul>
<h1>第八章 FAQ 常见问题</h1>

<h2>8.1 如何打一个可被rose识别的jar包</h2>

<p>如果你使用maven，在pom中添加如果定义之后，你打出来的jar包就会被rose扫描到并且引入到上下文环境中：</p>

<pre>    <code>
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-jar-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;archive&gt;
                    &lt;manifestEntries&gt;
                        &lt;Rose&gt;*&lt;/Rose&gt;
                    &lt;/manifestEntries&gt;
                &lt;/archive&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    </code>
</pre>
<ul>
    <li>用途：

        <ul>
            <li>比如说你做了一个拦截器，在多个项目里需要相同的逻辑，那只需要把这个拦截器做在一个jar包里，声明是rose支持的包即可被使用。</li>
        </ul>
    </li>
</ul>
<h2>8.2 被认成batch执行的sql返回</h2>

<p><strong>如果你的sql在执行update insert delete，并且dao的第一个参数是list类的多个值，那这条sql会被拆成多条sql依次执行，执行的结果会以各条sql的返回组成的数组返回。</strong></p>

<h2>8.3 一个良好的大型WEB项目架构实践</h2>

<p>我们一般会把项目规定为：<strong>controller/service/biz/dao层，</strong>不能跨层调用，只在service层允许同时调用子层多个方法。</p>

<h2>8.4 为何DAO不被初始化</h2>

<p>相当多的初次使用DAO的朋友，会将XXXDAO写作XXXDao，注意大小写，只有DAO全大写时的class，才会被jade认识并扫描入环境中。</p>

</article>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>


</body></html>